Merge branch 'v2-dev' into v2-dev-buy

This commit is contained in:
xiaojunnuo
2025-08-29 16:54:11 +08:00
78 changed files with 1559 additions and 225 deletions

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Bug Fixes
* 更新我爱云CDN域名地址和部分目录结构 [@tyjsjxh](https://github.com/tyjsjxh) ([#514](https://github.com/certd/certd/issues/514)) ([78e7a81](https://github.com/certd/certd/commit/78e7a81638c2ee779f0ab6c3ba7e5c6f6e064151))
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
* 修复proxmox某些情况下执行卡住的bug ([ebd6917](https://github.com/certd/certd/commit/ebd6917a1d40ae4d94555c32b7e3c093d0599b94))
### Performance Improvements
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
* 腾讯云EO插件支持自动获取zoneid和域名列表 ([70fcdc9](https://github.com/certd/certd/commit/70fcdc9ebbfb7c883c0c8a2138f61a0776a9491b))
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
* 支持部署到华为云obs ([9feb9d0](https://github.com/certd/certd/commit/9feb9d04b3c56ec95c06fcf4fd071eb0e88ffc6f))
* 支持部署到dokploy ([7dbdeae](https://github.com/certd/certd/commit/7dbdeaebe0bfee7521a863fe5e6b4a712aec5876))
* 支持删除宝塔证书夹中的过期证书 ([3575113](https://github.com/certd/certd/commit/3575113655be751d19f88c64491e98a89042d6a2))
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
* lecdnv2支持api token ([e448934](https://github.com/certd/certd/commit/e4489343fee7754be07bcfc3323969dc3a30e90c))
* openapi返回证书时挑选匹配范围最小的那一个增加format参数增加返回值p7b格式增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
### Bug Fixes

View File

@@ -1 +1 @@
23:58
00:43

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Bug Fixes
* 更新我爱云CDN域名地址和部分目录结构 [@tyjsjxh](https://github.com/tyjsjxh) ([#514](https://github.com/certd/certd/issues/514)) ([78e7a81](https://github.com/certd/certd/commit/78e7a81638c2ee779f0ab6c3ba7e5c6f6e064151))
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
* 修复proxmox某些情况下执行卡住的bug ([ebd6917](https://github.com/certd/certd/commit/ebd6917a1d40ae4d94555c32b7e3c093d0599b94))
### Performance Improvements
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
* 腾讯云EO插件支持自动获取zoneid和域名列表 ([70fcdc9](https://github.com/certd/certd/commit/70fcdc9ebbfb7c883c0c8a2138f61a0776a9491b))
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
* 支持部署到华为云obs ([9feb9d0](https://github.com/certd/certd/commit/9feb9d04b3c56ec95c06fcf4fd071eb0e88ffc6f))
* 支持部署到dokploy ([7dbdeae](https://github.com/certd/certd/commit/7dbdeaebe0bfee7521a863fe5e6b4a712aec5876))
* 支持删除宝塔证书夹中的过期证书 ([3575113](https://github.com/certd/certd/commit/3575113655be751d19f88c64491e98a89042d6a2))
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
* lecdnv2支持api token ([e448934](https://github.com/certd/certd/commit/e4489343fee7754be07bcfc3323969dc3a30e90c))
* openapi返回证书时挑选匹配范围最小的那一个增加format参数增加返回值p7b格式增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
### Bug Fixes

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.36.17"
"version": "1.36.18"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/publishlab/node-acme-client/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/acme-client
## [1.36.17](https://github.com/publishlab/node-acme-client/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.36.17",
"@certd/basic": "^1.36.18",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -69,5 +69,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/basic
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
23:53
00:39

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/pipeline
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,8 +17,8 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.17",
"@certd/plus-core": "^1.36.17",
"@certd/basic": "^1.36.18",
"@certd/plus-core": "^1.36.18",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"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": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.36.17",
"version": "1.36.18",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -61,5 +61,5 @@
"fetch"
]
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Performance Improvements
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.17",
"@certd/basic": "^1.36.18",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -90,7 +90,7 @@ export class K8sClient {
async createSecret(opts: { namespace: string; body: V1Secret }) {
const namespace = opts.namespace || "default";
const created = await this.client.createNamespacedSecret(namespace, opts.body);
this.logger.info("new secrets:", opts.body);
this.logger.info("new secrets:", opts.body.metadata);
return created.body;
}
@@ -103,17 +103,33 @@ export class K8sClient {
// return await this.client.replaceNamespacedSecret(secretName, namespace, opts.body);
// }
async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret }) {
async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret; createOnNotFound?: boolean }) {
const namespace = opts.namespace || "default";
const secretName = opts.secretName;
if (secretName == null) {
throw new Error("secretName 不能为空");
}
this.logger.info("patch secret:", secretName, namespace);
const oldSecret = await this.client.readNamespacedSecret(secretName, namespace);
let oldSecret: any = null;
try {
oldSecret = await this.client.readNamespacedSecret(secretName, namespace);
} catch (e) {
//@ts-ignore
if (e.response?.body?.code === 404) {
this.logger.warn(`secret ${secretName} 不存在`);
if (opts.createOnNotFound) {
//没有找到,则创建
const res = await this.createSecret({ namespace, body: opts.body });
this.logger.info(`secret ${secretName} 已创建`);
return res;
}
}
throw e;
}
const newSecret = _.merge(oldSecret.body, opts.body);
const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret);
this.logger.info("secret updated");
this.logger.info(`secret ${secretName} 已更新`);
return res.body;
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/lib-server
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/lib-server

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.36.17",
"version": "1.36.18",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Performance Improvements
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
* openapi返回证书时挑选匹配范围最小的那一个增加format参数增加返回值p7b格式增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,10 +16,10 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.36.17",
"@certd/basic": "^1.36.17",
"@certd/pipeline": "^1.36.17",
"@certd/plugin-lib": "^1.36.17",
"@certd/acme-client": "^1.36.18",
"@certd/basic": "^1.36.18",
"@certd/pipeline": "^1.36.18",
"@certd/plugin-lib": "^1.36.18",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -21,6 +21,15 @@ export type CertReaderHandleContext = {
};
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
const formats = {
pem: ["crt", "key", "ic"],
one: ["one"],
pfx: ["pfx"],
der: ["der"],
jks: ["jks"],
p7b: ["p7b", "key"],
};
export class CertReader {
cert: CertInfo;
@@ -74,8 +83,17 @@ export class CertReader {
return arr[0] + endStr;
}
toCertInfo(): CertInfo {
return this.cert;
toCertInfo(format?: string): CertInfo {
if (!format) {
return this.cert;
}
const formatArr = formats[format];
const res: any = {};
formatArr.forEach((key: string) => {
res[key] = this.cert[key];
});
return res;
}
getCrtDetail(crt: string = this.cert.crt) {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Performance Improvements
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
* ssh 配置sudo免密提示 ([e1e7011](https://github.com/certd/certd/commit/e1e7011853ad0c5bd7b09c3690861d5aa34b2db4))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.36.17",
"version": "1.36.18",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -21,8 +21,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.36.17",
"@certd/pipeline": "^1.36.17",
"@certd/basic": "^1.36.18",
"@certd/pipeline": "^1.36.18",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -53,5 +53,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "831c325c6383ba0a6f2dfa7496451ec714784e93"
"gitHead": "ea18a5ad151b296fda54fb5bcbe64c7d80cdff2f"
}

View File

@@ -7,9 +7,9 @@ export type AliyunClientV2Req = {
// 接口 HTTP 方法
method?: "GET" | "POST";
authType?: "AK";
style?: "RPC";
style?: "RPC" | "ROA";
// 接口 PATH
pathname?: `/`;
pathname?: string;
data?: any;
};
@@ -63,10 +63,10 @@ export class AliyunClientV2 {
protocol: "HTTPS",
// 接口 HTTP 方法
method: req.method ?? "POST",
authType: "AK",
style: "RPC",
authType: req.authType ?? "AK",
style: req.style ?? "RPC",
// 接口 PATH
pathname: `/`,
pathname: req.pathname ?? `/`,
// 接口请求体内容格式
reqBodyType: "json",
// 接口响应体内容格式

View File

@@ -9,7 +9,8 @@ export type AliyunCertInfo = {
export type AliyunSslClientOpts = {
access: AliyunAccess;
logger: ILogger;
endpoint: string;
endpoint?: string;
region?: string;
};
export type AliyunSslGetResourceListReq = {
@@ -48,10 +49,19 @@ export class AliyunSslClient {
async getClient() {
const access = this.opts.access;
const client = new AliyunClient({ logger: this.opts.logger });
let endpoint = this.opts.endpoint || "cas.aliyuncs.com";
if (this.opts.endpoint == null && this.opts.region) {
if (this.opts.region === "cn-hangzhou") {
endpoint = "cas.aliyuncs.com";
} else {
endpoint = `cas.${this.opts.region}.aliyuncs.com`;
}
}
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: `https://${this.opts.endpoint || "cas.aliyuncs.com"}`,
endpoint: `https://${endpoint}`,
apiVersion: "2020-04-07",
});
return client;

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Bug Fixes
* 修复cron选择组件星期显示错误的bug ([eb75e52](https://github.com/certd/certd/commit/eb75e52278f94a72643f7317e6740fb42666c68a))
### Performance Improvements
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
* openapi返回证书时挑选匹配范围最小的那一个增加format参数增加返回值p7b格式增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.36.17",
"version": "1.36.18",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -30,6 +30,7 @@
"@ant-design/icons-vue": "^7.0.1",
"@aws-sdk/client-s3": "^3.535.0",
"@aws-sdk/s3-request-presigner": "^3.535.0",
"@certd/vue-js-cron-light": "^4.0.14",
"@ctrl/tinycolor": "^4.1.0",
"@fast-crud/fast-crud": "^1.25.13",
"@fast-crud/fast-extends": "^1.25.13",
@@ -43,7 +44,6 @@
"@tailwindcss/typography": "^0.5.16",
"@tanstack/vue-store": "^0.7.0",
"@vee-validate/zod": "^4.15.0",
"@certd/vue-js-cron-light": "^4.0.14",
"@vue/shared": "^3.5.13",
"@vueuse/core": "^10.11.0",
"ant-design-vue": "^4.2.6",
@@ -103,8 +103,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.36.17",
"@certd/pipeline": "^1.36.17",
"@certd/lib-iframe": "^1.36.18",
"@certd/pipeline": "^1.36.18",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -564,7 +564,7 @@ export default {
ipv6Priority: "IPv6 Priority",
dualStackNetworkHelper: "If IPv6 priority is selected, enable IPv6 in docker-compose.yaml",
enableCommonCnameService: "Enable Public CNAME Service",
commonCnameHelper: "Allow use of public CNAME service. If disabled and no <router-link to='/sys/cname/provider'>custom CNAME service</router-link> is set, CNAME proxy certificate application will not work.",
commonCnameHelper: "Allow use of public CNAME service. If disabled and no <a href='#/sys/cname/provider'>custom CNAME service</a> is set, CNAME proxy certificate application will not work.",
enableCommonSelfServicePasswordRetrieval: "Enable self-service password recovery",
saveButton: "Save",
stopSuccess: "Stopped successfully",
@@ -587,6 +587,7 @@ export default {
commFeature: "Commercial feature",
smsProvider: "SMS provider",
aliyunSms: "Aliyun SMS",
tencentSms: "Tencent SMS",
yfySms: "YFY SMS",
smsTest: "SMS test",
testMobilePlaceholder: "Enter test mobile number",

View File

@@ -570,7 +570,7 @@ export default {
ipv6Priority: "IPV6优先",
dualStackNetworkHelper: "如果选择IPv6优先需要在docker-compose.yaml中启用ipv6",
enableCommonCnameService: "启用公共CNAME服务",
commonCnameHelper: "是否可以使用公共CNAME服务如果禁用且没有设置<router-link to='/sys/cname/provider'>自定义CNAME服务</router-link>则无法使用CNAME代理方式申请证书",
commonCnameHelper: "是否可以使用公共CNAME服务如果禁用且没有设置<a href='#/sys/cname/provider'>自定义CNAME服务</a>则无法使用CNAME代理方式申请证书",
enableCommonSelfServicePasswordRetrieval: "启用自助找回密码",
saveButton: "保存",
stopSuccess: "停止成功",
@@ -593,6 +593,7 @@ export default {
commFeature: "商业版功能",
smsProvider: "短信提供商",
aliyunSms: "阿里云短信",
tencentSms: "腾讯云短信",
yfySms: "易发云短信",
smsTest: "短信测试",
testMobilePlaceholder: "输入测试手机号",

View File

@@ -133,7 +133,7 @@ export const sysResources = [
title: "certd.sysResources.sysPluginConfig",
name: "SysPluginConfig",
path: "/sys/plugin/config",
component: "/sys/plugin/config.vue",
component: "/sys/plugin/config-common.vue",
meta: {
show: () => {
const settingStore = useSettingStore();

View File

@@ -1,9 +1,11 @@
import { defineStore } from "pinia";
import * as api from "./api.plugin";
import { DynamicType, FormItemProps } from "@fast-crud/fast-crud";
import { DynamicType, FormItemProps, useMerge } from "@fast-crud/fast-crud";
import { i18n } from "/src/locales/i18n";
import { cloneDeep } from "lodash-es";
interface PluginState {
group?: PluginGroups;
originGroup?: PluginGroups;
}
export type PluginGroup = {
@@ -32,14 +34,17 @@ export class PluginGroups {
groups!: { [key: string]: PluginGroup };
map!: { [key: string]: PluginDefine };
t: any;
constructor(groups: { [key: string]: PluginGroup }) {
mergeSetting?: boolean;
constructor(groups: { [key: string]: PluginGroup }, opts?: { mergeSetting?: boolean }) {
this.groups = groups;
this.t = i18n.global.t;
this.mergeSetting = opts?.mergeSetting ?? false;
this.initGroup(groups);
this.initMap();
}
private initGroup(groups: { [p: string]: PluginGroup }) {
const { merge } = useMerge();
const all: PluginGroup = {
key: "all",
title: this.t("certd.all"),
@@ -48,6 +53,14 @@ export class PluginGroups {
icon: "material-symbols:border-all-rounded",
};
for (const key in groups) {
if (this.mergeSetting) {
for (const plugin of groups[key].plugins) {
if (plugin.sysSetting) {
merge(plugin.input, plugin.sysSetting.metadata?.input || {});
}
}
}
all.plugins.push(...groups[key].plugins);
}
this.groups = {
@@ -132,11 +145,15 @@ export const usePluginStore = defineStore({
id: "app.plugin",
state: (): PluginState => ({
group: null,
originGroup: null,
}),
actions: {
async reload() {
const groups = await api.GetGroups({});
this.group = new PluginGroups(groups);
this.group = new PluginGroups(groups, { mergeSetting: true });
this.originGroup = new PluginGroups(cloneDeep(groups));
console.log("group", this.group);
console.log("originGroup", this.originGroup);
},
async init() {
if (!this.group) {
@@ -150,6 +167,7 @@ export const usePluginStore = defineStore({
},
async clear() {
this.group = null;
this.originGroup = null
},
async getList(): Promise<PluginDefine[]> {
await this.init();
@@ -159,6 +177,10 @@ export const usePluginStore = defineStore({
await this.init();
return this.group.get(name);
},
async getPluginDefineFromOrigin(name: string): Promise<PluginDefine> {
await this.init();
return this.originGroup.get(name);
},
async getPluginConfig(query: any) {
return await api.GetPluginConfig(query);
},

View File

@@ -304,3 +304,11 @@ h6 {
padding: 10px;
color: #6e6e6e;
}
.ant-modal-body{
.fs-form-body{
max-height: 66vh;
overflow-y: auto;
}
}

View File

@@ -84,6 +84,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
component: {
color: "auto",
},
order: -1,
},
form: {
component: {

View File

@@ -82,6 +82,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
column: {
width: 300,
order: -11,
},
},
from: {

View File

@@ -138,6 +138,7 @@ export function useCertPipelineCreator() {
form: {
doSubmit,
wrapper: {
wrapClassName: "cert_pipeline_create_form",
width: 1350,
saveRemind: false,
title: t("certd.pipelineForm.createTitle"),

View File

@@ -115,4 +115,13 @@ function batchRerun() {
padding-left: 10px;
}
}
.cert_pipeline_create_form {
.ant-collapse {
margin: 10px;
}
.ant-collapse-header {
text-align: right;
}
}
</style>

View File

@@ -43,7 +43,7 @@
</a-tab-pane>
</a-tabs>
<a-form-item>
<a-button type="primary" size="large" html-type="submit" :loading="loading" class="login-button">
<a-button type="primary" size="large" html-type="button" :loading="loading" class="login-button" @click="handleFinish">
{{ t("authentication.loginButton") }}
</a-button>
@@ -217,7 +217,6 @@ export default defineComponent({
</script>
<style lang="less">
.login-page.main {
//margin: 20px !important;
margin-bottom: 100px;

View File

@@ -97,6 +97,7 @@ export type CertApplyPluginSysInput = {
export type PluginSysSetting<T> = {
sysSetting: {
input?: T;
metadata?: Record<string, any>;
};
};
export type CommPluginConfig = {
@@ -118,6 +119,14 @@ export async function SaveCommPluginConfigs(data: CommPluginConfig): Promise<voi
});
}
export async function savePluginSetting(req: { name: string; sysSetting: any }): Promise<void> {
return await request({
url: apiPrefix + "/saveSetting",
method: "post",
data: req,
});
}
export async function DoTest(req: { id: number; input: any }): Promise<void> {
return await request({
url: apiPrefix + "/doTest",

View File

@@ -0,0 +1,184 @@
<template>
<div class="plugin-config">
<div class="origin-metadata w-100%">
<div class="block-title">
自定义插件参数配置
<div class="helper">可以设置插件选项的配置设置配置默认值修改帮助说明设置是否显示该字段等</div>
</div>
<div class="p-10">
<div ref="formRef" class="config-form w-full" :label-col="labelCol" :wrapper-col="wrapperCol">
<table class="table-fixed w-full">
<thead>
<tr>
<th class="text-left p-5" width="200px">插件参数</th>
<th class="text-left p-5" width="100px">参数配置</th>
<th class="text-left flex-1 p-5">自定义</th>
</tr>
</thead>
<tbody>
<template v-for="item in originInputs" :key="item.key">
<template v-for="prop in editableKeys" :key="prop.key">
<tr>
<td v-if="prop.key === 'value'" class="border-t-2 p-5" rowspan="3" :class="{ 'border-t-2': prop.key === 'value' }">{{ item.title }}</td>
<td class="border-t p-5" :class="{ 'border-t-2': prop.key === 'value' }">{{ prop.label }}</td>
<td class="border-t p-5" :class="{ 'border-t-2': prop.key === 'value' }">
<rollbackable :value="configForm[item.key][prop.key]" @set="prop.onSet(item)" @clear="delete configForm[item.key][prop.key]">
<template #default>
<fs-render :render-func="prop.defaultRender(item)"></fs-render>
</template>
<template #edit>
<fs-render :render-func="prop.editRender(item)"></fs-render>
</template>
</rollbackable>
</td>
</tr>
</template>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
import { computed, nextTick, onMounted, reactive, ref, Ref, unref } from "vue";
import { useRoute, useRouter } from "vue-router";
import * as api from "./api";
import { usePluginStore } from "/@/store/plugin";
import { cloneDeep, get, merge, set, unset } from "lodash-es";
import Rollbackable from "./rollbackable.vue";
import { FsRender } from "@fast-crud/fast-crud";
const route = useRoute();
const router = useRouter();
const pluginStore = usePluginStore();
const props = defineProps<{
plugin: any;
}>();
const pluginMetadata = ref<any>("");
const currentPlugin = ref();
const labelCol = ref({
span: null,
style: {
width: "145px",
},
});
const wrapperCol = ref({ span: 16 });
const configForm: any = reactive({});
function getScope() {
return {
form: configForm,
};
}
function getForm() {
return configForm;
}
const editableKeys = ref([
{
key: "value",
label: "默认值",
onSet(item: any) {
configForm[item.key]["value"] = item.value ?? null;
},
defaultRender(item: any) {
return () => {
return item["value"] ?? "";
};
},
editRender(item: any) {
return () => {
return <fs-component-render {...item.component} vModel:modelValue={configForm[item.key]["value"]} scope={getScope()} />;
};
},
},
{
key: "show",
label: "是否显示",
onSet(item: any) {
configForm[item.key]["show"] = item.show ?? true;
},
defaultRender(item: any) {
return () => {
const value = item["show"];
return value === false ? "不显示" : "显示";
};
},
editRender(item: any) {
return () => {
return <a-switch vModel:checked={configForm[item.key]["show"]} />;
};
},
},
{
key: "helper",
label: "帮助说明",
onSet(item: any) {
configForm[item.key]["helper"] = item.helper ?? "";
},
defaultRender(item: any) {
return () => {
return <pre class={"helper"}>{item["helper"]}</pre>;
};
},
editRender(item: any) {
return () => {
return <a-textarea rows={5} vModel:value={configForm[item.key]["helper"]} />;
};
},
},
]);
const originInputs = computed(() => {
if (!currentPlugin.value) {
return;
}
const input = cloneDeep(currentPlugin.value.input);
const newInputs: any = {};
for (const key in input) {
const value = input[key];
value.key = key;
const newInput: any = cloneDeep(value);
newInputs[key] = newInput;
}
return newInputs;
});
function clearFormValue(key: string) {
unset(configForm, key);
console.log(key, configForm);
}
async function loadPluginSetting() {
currentPlugin.value = await pluginStore.getPluginDefineFromOrigin(props.plugin.name);
for (const key in currentPlugin.value.input) {
configForm[key] = {};
}
const setting = props.plugin.sysSetting;
if (setting) {
const settingJson = JSON.parse(setting);
merge(configForm, settingJson.metadata?.input || {});
}
}
onMounted(async () => {
await loadPluginSetting();
});
defineExpose({
getForm,
});
</script>
<style lang="less">
.plugin-config {
pre {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
}
</style>

View File

@@ -1,11 +1,14 @@
import * as api from "./api";
import { useI18n } from "/src/locales";
import { Ref, ref } from "vue";
import { Ref, ref, computed } from "vue";
import { useRouter } from "vue-router";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { Modal, notification } from "ant-design-vue";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { Modal } from "ant-design-vue";
//@ts-ignore
import yaml from "js-yaml";
import { usePluginImport } from "./use-import";
import { usePluginConfig } from "./use-config";
import { useSettingStore } from "/src/store/settings/index";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -35,75 +38,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const { openCrudFormDialog } = useFormWrapper();
async function openImportDialog() {
function createCrudOptions() {
return {
crudOptions: {
columns: {
content: {
title: t("certd.pluginFile"),
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 8,
},
},
col: {
span: 24,
},
helper: t("certd.selectPluginFile"),
},
},
override: {
title: t("certd.overrideSameName"),
type: "dict-switch",
dict: dict({
data: [
{
value: true,
label: t("certd.override"),
},
{
value: false,
label: t("certd.noOverride"),
},
],
}),
form: {
value: false,
col: {
span: 24,
},
helper: t("certd.overrideHelper"),
},
},
},
form: {
wrapper: {
title: t("certd.importPlugin"),
saveRemind: false,
},
afterSubmit() {
notification.success({ message: t("certd.operationSuccess") });
crudExpose.doRefresh();
},
async doSubmit({ form }: any) {
return await api.ImportPlugin({
...form,
});
},
},
},
};
}
const { crudOptions } = createCrudOptions();
await openCrudFormDialog({ crudOptions });
}
const { openImportDialog } = usePluginImport();
const { openConfigDialog } = usePluginConfig();
const settingStore = useSettingStore();
return {
crudOptions: {
settings: {
@@ -139,7 +78,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
text: t("certd.import"),
type: "primary",
async click() {
await openImportDialog();
await openImportDialog({ crudExpose });
},
},
},
@@ -186,6 +125,21 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}
},
},
config: {
show: computed(() => {
return settingStore.isComm;
}),
text: null,
icon: "ion:settings-outline",
title: t("certd.config"),
type: "link",
async click({ row }) {
await openConfigDialog({
row,
crudExpose,
});
},
},
},
},
table: {

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import { defineProps } from "vue";
const props = defineProps<{ value: any }>();
const emits = defineEmits(["set", "clear"]);
function setValue() {
emits("set");
}
function clearValue() {
emits("clear");
}
</script>
<template>
<div class="rollbackable">
<div class="flex">
<div style="width: 100px">
<a-tag v-if="value === undefined" color="green" size="small" class="pointer flex-inline items-center" @click.stop="setValue">
<fs-icon icon="material-symbols:edit" class="mr-5"></fs-icon>
自定义
</a-tag>
<a-tag v-else color="red" size="small" class="pointer flex-inline items-center" @click.stop="clearValue">
<fs-icon icon="material-symbols:undo" class="mr-5"></fs-icon>
还原
</a-tag>
</div>
<div class="flex-1 overflow-hidden value-render">
<slot v-if="value === undefined" name="default"></slot>
<slot v-else name="edit"></slot>
</div>
</div>
</div>
</template>
<style lang="less">
.rollbackable {
.value-render {
.ant-select,
.ant-input {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,79 @@
import * as api from "/@/views/sys/plugin/api";
import { useFormWrapper } from "@fast-crud/fast-crud";
import { useI18n } from "/@/locales";
import { notification } from "ant-design-vue";
import ConfigEditor from "./config-editor.vue";
import { ref } from "vue";
import { usePluginStore } from "/@/store/plugin";
export function usePluginConfig() {
const { openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
const pluginStore = usePluginStore();
// @ts-ignore
async function openConfigDialog({ row, crudExpose }) {
const configEditorRef = ref();
function createCrudOptions() {
return {
crudOptions: {
columns: {},
form: {
wrapper: {
width: "80%",
title: "插件参数自定义",
saveRemind: false,
slots: {
"form-body-top": () => {
return (
<div>
<ConfigEditor ref={configEditorRef} plugin={row}></ConfigEditor>
</div>
);
},
},
},
afterSubmit() {
notification.success({ message: t("certd.operationSuccess") });
crudExpose.doRefresh();
},
async doSubmit({}: any) {
const form = configEditorRef.value.getForm();
const newForm: any = {};
for (const key in form) {
const value = form[key];
if (value && Object.keys(value).length > 0) {
newForm[key] = value;
}
}
const res = await api.savePluginSetting({
name: row.name,
sysSetting: {
metadata: {
input: newForm,
},
},
});
await pluginStore.clear();
return res;
},
},
},
};
}
const { crudOptions } = createCrudOptions();
await openCrudFormDialog({ crudOptions });
// modal.confirm({
// title: "插件元数据配置",
// width: "80%",
// content: () => {
// return <ConfigEditor plugin={row}></ConfigEditor>;
// },
// });
}
return {
openConfigDialog,
};
}

View File

@@ -0,0 +1,80 @@
import * as api from "/@/views/sys/plugin/api";
import { useFormWrapper } from "@fast-crud/fast-crud";
import { useI18n } from "/@/locales";
import { Modal, notification } from "ant-design-vue";
export function usePluginImport() {
const { openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
async function openImportDialog({ crudExpose }) {
function createCrudOptions() {
return {
crudOptions: {
columns: {
content: {
title: t("certd.pluginFile"),
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 8,
},
},
col: {
span: 24,
},
helper: t("certd.selectPluginFile"),
},
},
override: {
title: t("certd.overrideSameName"),
type: "dict-switch",
dict: dict({
data: [
{
value: true,
label: t("certd.override"),
},
{
value: false,
label: t("certd.noOverride"),
},
],
}),
form: {
value: false,
col: {
span: 24,
},
helper: t("certd.overrideHelper"),
},
},
},
form: {
wrapper: {
title: t("certd.importPlugin"),
saveRemind: false,
},
afterSubmit() {
notification.success({ message: t("certd.operationSuccess") });
crudExpose.doRefresh();
},
async doSubmit({ form }: any) {
return await api.ImportPlugin({
...form,
});
},
},
},
};
}
const { crudOptions } = createCrudOptions();
await openCrudFormDialog({ crudOptions });
}
return {
openImportDialog,
};
}

View File

@@ -45,6 +45,7 @@
<a-form-item :label="t('certd.smsProvider')" :name="['private', 'sms', 'type']">
<a-select v-model:value="formState.private.sms.type" @change="smsTypeChange">
<a-select-option value="aliyun">{{ t("certd.aliyunSms") }}</a-select-option>
<a-select-option value="tencent">{{ t("certd.tencentSms") }}</a-select-option>
<a-select-option value="yfysms">{{ t("certd.yfySms") }}</a-select-option>
</a-select>
</a-form-item>

View File

@@ -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.36.18](https://github.com/certd/certd/compare/v1.36.17...v1.36.18) (2025-08-28)
### Bug Fixes
* 更新我爱云CDN域名地址和部分目录结构 [@tyjsjxh](https://github.com/tyjsjxh) ([#514](https://github.com/certd/certd/issues/514)) ([78e7a81](https://github.com/certd/certd/commit/78e7a81638c2ee779f0ab6c3ba7e5c6f6e064151))
* 修复proxmox某些情况下执行卡住的bug ([ebd6917](https://github.com/certd/certd/commit/ebd6917a1d40ae4d94555c32b7e3c093d0599b94))
### Performance Improvements
* 部署到k8s支持自动创建secret ([c09c962](https://github.com/certd/certd/commit/c09c962cb676ca261610aa9f3e5105c9dae43f43))
* 短信验证码支持腾讯云 ([9108459](https://github.com/certd/certd/commit/9108459ae42bcd95a59acba164a64e82e5f2cfe6))
* 商业版支持自定义插件的参数配置 ([17f23f3](https://github.com/certd/certd/commit/17f23f37516af925d5049291d67d41e4271f81f8))
* 腾讯云插件支持国际版 ([58e82d5](https://github.com/certd/certd/commit/58e82d5dbd4ebf089ef239578ef9b68454d17b30))
* 腾讯云EO插件支持自动获取zoneid和域名列表 ([70fcdc9](https://github.com/certd/certd/commit/70fcdc9ebbfb7c883c0c8a2138f61a0776a9491b))
* 支持部署到阿里云云原生API网关、AI网关 ([2ca20be](https://github.com/certd/certd/commit/2ca20be197720201fceabcce9d927f4dbc1cc872))
* 支持部署到华为云obs ([9feb9d0](https://github.com/certd/certd/commit/9feb9d04b3c56ec95c06fcf4fd071eb0e88ffc6f))
* 支持部署到dokploy ([7dbdeae](https://github.com/certd/certd/commit/7dbdeaebe0bfee7521a863fe5e6b4a712aec5876))
* 支持删除宝塔证书夹中的过期证书 ([3575113](https://github.com/certd/certd/commit/3575113655be751d19f88c64491e98a89042d6a2))
* 支持p7b证书格式 ([d9f4a57](https://github.com/certd/certd/commit/d9f4a5793d68a017a5d80ad5385cbda603c4e165))
* lecdnv2支持api token ([e448934](https://github.com/certd/certd/commit/e4489343fee7754be07bcfc3323969dc3a30e90c))
* openapi返回证书时挑选匹配范围最小的那一个增加format参数增加返回值p7b格式增加detail返回 ([2085bcc](https://github.com/certd/certd/commit/2085bcceb61c3723c9bdfec4c4cc0917631ff5e5))
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
### Bug Fixes

View File

@@ -9,8 +9,6 @@
```
```shell
npm run heap
```

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.36.17",
"version": "1.36.18",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -42,20 +42,20 @@
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.36.17",
"@certd/basic": "^1.36.17",
"@certd/commercial-core": "^1.36.17",
"@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/jdcloud": "^1.36.17",
"@certd/lib-huawei": "^1.36.17",
"@certd/lib-k8s": "^1.36.17",
"@certd/lib-server": "^1.36.17",
"@certd/midway-flyway-js": "^1.36.17",
"@certd/pipeline": "^1.36.17",
"@certd/plugin-cert": "^1.36.17",
"@certd/plugin-lib": "^1.36.17",
"@certd/plugin-plus": "^1.36.17",
"@certd/plus-core": "^1.36.17",
"@certd/acme-client": "^1.36.18",
"@certd/basic": "^1.36.18",
"@certd/commercial-core": "^1.36.18",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.36.18",
"@certd/lib-huawei": "^1.36.18",
"@certd/lib-k8s": "^1.36.18",
"@certd/lib-server": "^1.36.18",
"@certd/midway-flyway-js": "^1.36.18",
"@certd/pipeline": "^1.36.18",
"@certd/plugin-cert": "^1.36.18",
"@certd/plugin-lib": "^1.36.18",
"@certd/plugin-plus": "^1.36.18",
"@certd/plus-core": "^1.36.18",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",

View File

@@ -0,0 +1,21 @@
import {Controller, Get, Provide} from '@midwayjs/core';
import {BaseController, Constants} from '@certd/lib-server';
/**
*/
@Provide()
@Controller('/health')
export class HealthController extends BaseController {
@Get('/liveliness', { summary: Constants.per.guest })
async liveliness(): Promise<any> {
return this.ok('ok')
}
@Get('/readiness', { summary: Constants.per.guest })
async readiness(): Promise<any> {
return this.ok('ok')
}
}

View File

@@ -10,6 +10,7 @@ export type CertGetReq = {
domains?: string;
certId: number;
autoApply?:boolean;
format?:string; //默认是所有,pem,der,p12,pfx,jks,one,p7b
};
/**
@@ -38,6 +39,7 @@ export class OpenCertController extends BaseOpenController {
domains: req.domains,
certId: req.certId,
autoApply: req.autoApply??false,
format: req.format
});
return this.ok(res);
}

View File

@@ -2,7 +2,11 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/c
import { merge } from 'lodash-es';
import { CrudController } from '@certd/lib-server';
import { PluginImportReq, PluginService } from "../../../modules/plugin/service/plugin-service.js";
import { CommPluginConfig, PluginConfigService } from '../../../modules/plugin/service/plugin-config-service.js';
import {
CommPluginConfig,
PluginConfig,
PluginConfigService
} from '../../../modules/plugin/service/plugin-config-service.js';
/**
* 插件
*/
@@ -79,7 +83,11 @@ export class PluginController extends CrudController<PluginService> {
const res = await this.pluginConfigService.saveCommPluginConfig(body);
return this.ok(res);
}
@Post('/saveSetting', { summary: 'sys:settings:edit' })
async saveSetting(@Body(ALL) body: PluginConfig) {
const res = await this.pluginConfigService.savePluginConfig(body);
return this.ok(res);
}
@Post('/import', { summary: 'sys:settings:edit' })
async import(@Body(ALL) body: PluginImportReq) {

View File

@@ -164,7 +164,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
@Post('/getSmsTypeDefine', { summary: 'sys:settings:view' })
async getSmsTypeDefine(@Body('type') type: string) {
return this.ok(SmsServiceFactory.getDefine(type));
const define =await SmsServiceFactory.getDefine(type);
return this.ok(define);
}

View File

@@ -83,7 +83,7 @@ export class CodeService {
}
const smsType = sysSettings.sms.type;
const smsConfig = sysSettings.sms.config;
const sender: ISmsService = SmsServiceFactory.createSmsService(smsType);
const sender: ISmsService = await SmsServiceFactory.createSmsService(smsType);
const accessGetter = new AccessSysGetter(this.accessService);
sender.setCtx({
accessService: accessGetter,

View File

@@ -1,25 +1,28 @@
import { AliyunSmsService } from './aliyun-sms.js';
import { YfySmsService } from './yfy-sms.js';
export class SmsServiceFactory {
static createSmsService(type: string) {
const cls = this.GetClassByType(type);
static async createSmsService(type: string) {
const cls = await this.GetClassByType(type);
return new cls();
}
static GetClassByType(type: string) {
static async GetClassByType(type: string) {
switch (type) {
case 'aliyun':
const {AliyunSmsService} = await import("./aliyun-sms.js")
return AliyunSmsService;
case 'yfysms':
const {YfySmsService} = await import("./yfy-sms.js")
return YfySmsService;
case 'tencent':
const {TencentSmsService} = await import("./tencent-sms.js")
return TencentSmsService;
default:
throw new Error('不支持的短信服务类型');
}
}
static getDefine(type: string) {
const cls = this.GetClassByType(type);
static async getDefine(type: string) {
const cls = await this.GetClassByType(type);
return cls.getDefine();
}
}

View File

@@ -0,0 +1,124 @@
import {ISmsService, PluginInputs, SmsPluginCtx} from './api.js';
import {TencentAccess} from "@certd/plugin-lib";
export type TencentSmsConfig = {
accessId: string;
signName: string;
codeTemplateId: string;
appId: string;
region: string;
};
export class TencentSmsService implements ISmsService {
static getDefine() {
return {
name: 'tencent',
desc: '腾讯云短信服务',
input: {
accessId: {
title: '腾讯云授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
},
region: {
title: '区域',
value:"ap-beijing",
component: {
name: 'a-select',
vModel: 'value',
options:[
{value:"ap-beijing",label:"华北地区(北京)"},
{value:"ap-guangzhou",label:"华南地区(广州)"},
{value:"ap-nanjing",label:"华东地区(南京)"},
]
},
helper:"随便选一个",
required: true,
},
signName: {
title: '签名',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
appId: {
title: '应用ID',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
codeTemplateId: {
title: '验证码模板Id',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
} as PluginInputs<TencentSmsConfig>,
};
}
ctx: SmsPluginCtx<TencentSmsConfig>;
setCtx(ctx: any) {
this.ctx = ctx;
}
async getClient() {
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/sms/v20210111/index.js');
const client = sdk.v20210111.Client;
const access = await this.ctx.accessService.getById<TencentAccess>(this.ctx.config.accessId);
// const region = this.region;
const clientConfig = {
credential: {
secretId: access.secretId,
secretKey: access.secretKey,
},
region: this.ctx.config.region,
profile: {
httpProfile: {
endpoint: `sms.${access.intlDomain()}tencentcloudapi.com`,
},
},
};
return new client(clientConfig);
}
async sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }) {
const { mobile, code, phoneCode } = opts;
const client = await this.getClient();
const smsConfig = this.ctx.config;
const params = {
"PhoneNumberSet": [
`+${phoneCode}${mobile}`
],
"SmsSdkAppId": smsConfig.appId,
"TemplateId": smsConfig.codeTemplateId,
"SignName": smsConfig.signName,
"TemplateParamSet": [
code
]
};
const ret = await client.SendSms(params);
this.checkRet(ret);
}
checkRet(ret: any) {
if (!ret || ret.Error) {
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message);
}
}
}

View File

@@ -27,7 +27,7 @@ export class CertInfoFacade {
@Inject()
userSettingsService : UserSettingsService
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean }) {
async getCertInfo(req: { domains?: string; certId?: number; userId: number,autoApply?:boolean,format?:string }) {
const { domains, certId, userId } = req;
if (certId) {
return await this.certInfoService.getCertInfoById({ id: certId, userId });
@@ -41,7 +41,7 @@ export class CertInfoFacade {
const domainArr = domains.split(',');
const matchedList = await this.certInfoService.getMatchCertList({domains:domainArr,userId})
let matched: CertInfoEntity = null
if (matchedList.length === 0 ) {
if(req.autoApply === true){
//自动申请,先创建自动申请流水线
@@ -54,13 +54,7 @@ export class CertInfoFacade {
});
}
}
matched = null;
for (const item of matchedList) {
if (item.expiresTime>0 && item.expiresTime > new Date().getTime()) {
matched = item;
break
}
}
let matched = this.getMinixMatched(matchedList);
if (!matched) {
if(req.autoApply === true){
//如果没有找到有效期内的证书,则自动触发一次申请
@@ -75,7 +69,38 @@ export class CertInfoFacade {
}
}
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId });
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId,format:req.format });
}
public getMinixMatched(matchedList: CertInfoEntity[]) {
let matched: CertInfoEntity = null;
for (const item of matchedList) {
if (item.expiresTime > 0 && item.expiresTime > new Date().getTime()) {
if (matched) {
//如果前面已经有match的值判断范围是否比上一个小
const currentStars = `-${item.domains}`.split("*");
const matchedStars = `-${matched.domains}`.split("*");
const currentLength = item.domains.split(",");
const matchedLength = matched.domains.split(",");
if (currentStars.length < matchedStars.length) {
//如果*的数量比上一个少,则替换为当前
matched = item;
} else if (currentStars.length == matchedStars.length) {
//如果*的数量相同,则比较域名数量
if (currentLength.length < matchedLength.length) {
matched = item;
}
}
} else {
matched = item;
}
}
}
return matched;
}
async createAutoPipeline(req:{domains:string[],userId:number}){

View File

@@ -113,7 +113,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async getCertInfoById(req: { id: number; userId: number }) {
async getCertInfoById(req: { id: number; userId: number,format?:string }) {
const entity = await this.info(req.id);
if (!entity || entity.userId !== req.userId) {
throw new CodeException(Constants.res.openCertNotFound);
@@ -124,7 +124,14 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}
const certInfo = JSON.parse(entity.certInfo) as CertInfo;
const certReader = new CertReader(certInfo);
return certReader.toCertInfo();
return {
...certReader.toCertInfo(req.format),
detail: {
id: entity.id,
domains: entity.domains.split(','),
notAfter: certReader.expires,
},
};
}
async updateCertByPipelineId(pipelineId: number, cert: CertInfo,file?:string,fromType = 'pipeline') {

View File

@@ -3,9 +3,10 @@ import { PluginService } from './plugin-service.js';
export type PluginConfig = {
name: string;
disabled: boolean;
disabled?: boolean;
sysSetting: {
input?: Record<string, any>;
metadata?: Record<string, any>;
};
};
@@ -37,10 +38,12 @@ export class PluginConfigService {
}
async saveCommPluginConfig(config: CommPluginConfig) {
await this.savePluginConfig('CertApply', config.CertApply);
config.CertApply.name = 'CertApply';
await this.savePluginConfig(config.CertApply);
}
async savePluginConfig(name: string, config: PluginConfig) {
async savePluginConfig( config: PluginConfig) {
const name = config.name;
const sysSetting = config?.sysSetting;
if (!sysSetting) {
throw new Error(`${name}.sysSetting is required`);
@@ -57,7 +60,14 @@ export class PluginConfigService {
author: "certd",
});
} else {
await this.pluginService.getRepository().update({ name }, { sysSetting: JSON.stringify(sysSetting) });
let setting = JSON.parse(pluginEntity.sysSetting || "{}");
if (sysSetting.metadata) {
setting.metadata = sysSetting.metadata;
}
if (sysSetting.input) {
setting.input = sysSetting.input;
}
await this.pluginService.getRepository().update({ name }, { sysSetting: JSON.stringify(setting) });
}
}

View File

@@ -1,16 +1,16 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, PageReq } from "@certd/lib-server";
import { PluginEntity } from "../entity/plugin.js";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { isComm } from "@certd/plus-core";
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
import { merge } from "lodash-es";
import { accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
import { dnsProviderRegistry } from "@certd/plugin-cert";
import { logger } from "@certd/basic";
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, PageReq} from "@certd/lib-server";
import {PluginEntity} from "../entity/plugin.js";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {IsNull, Not, Repository} from "typeorm";
import {isComm} from "@certd/plus-core";
import {BuiltInPluginService} from "../../pipeline/service/builtin-plugin-service.js";
import {merge} from "lodash-es";
import {accessRegistry, notificationRegistry, pluginRegistry} from "@certd/pipeline";
import {dnsProviderRegistry} from "@certd/plugin-cert";
import {logger} from "@certd/basic";
import yaml from "js-yaml";
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
import {getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin} from "./default-plugin.js";
import fs from "fs";
import path from "path";
@@ -57,9 +57,9 @@ export class PluginService extends BaseService<PluginEntity> {
};
}
async getEnabledBuildInGroup(isSimple = false) {
async getEnabledBuildInGroup(opts?:{isSimple?:boolean,withSetting?:boolean}) {
const groups = this.builtInPluginService.getGroups();
if (isSimple) {
if (opts?.isSimple) {
for (const key in groups) {
const group = groups[key];
group.plugins.forEach(item => {
@@ -72,9 +72,43 @@ export class PluginService extends BaseService<PluginEntity> {
if (!isComm()) {
return groups;
}
// 初始化设置
const settingPlugins = await this.repository.find({
select:{
id:true,
name:true,
sysSetting:true
},
where: {
sysSetting : Not(IsNull())
}
})
//合并插件配置
const pluginSettingMap:any = {}
for (const item of settingPlugins) {
if (!item.sysSetting) {
continue;
}
pluginSettingMap[item.name] = JSON.parse(item.sysSetting);
}
for (const key in groups) {
const group = groups[key];
if (!group.plugins) {
continue;
}
for (const item of group.plugins) {
const pluginSetting = pluginSettingMap[item.name];
if (pluginSetting){
item.sysSetting = pluginSetting
}
}
}
//排除禁用的
const list = await this.list({
query: {
type: "builtIn",
disabled: true
}
});

View File

@@ -33,3 +33,4 @@ export * from './plugin-wangsu/index.js'
export * from './plugin-admin/index.js'
export * from './plugin-ksyun/index.js'
export * from './plugin-apisix/index.js'
export * from './plugin-dokploy/index.js'

View File

@@ -0,0 +1,272 @@
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import {
AliyunAccess,
AliyunSslClient,
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunApig',
title: '阿里云-部署至云原生API网关/AI网关',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
desc: '自动部署域名证书至云原生API网关、AI网关',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class DeployCertToAliyunApig extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
},
required: true,
})
cert!: CertInfo | string;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权',
component: {
name: 'access-selector',
type: 'aliyun',
},
required: true,
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '区域',
helper: '请选择区域',
action: DeployCertToAliyunApig.prototype.onGetRegionList.name,
watches: ['certDomains', 'accessId'],
required: true,
component:{
name:"remote-auto-complete"
}
})
)
regionEndpoint!: string;
@TaskInput({
title: "网关类型",
component: {
name: "a-select",
vModel:"value",
options:[
{value:"AI",label:"AI"},
{value:"API",label:"API"},
]
},
required: true //必填
})
gatewayType!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '绑定域名',
helper: '请选择域名',
action: DeployCertToAliyunApig.prototype.onGetDomainList.name,
watches: ['region', 'accessId','gatewayType'],
required: true,
})
)
domainList!: string[];
@TaskInput({
title: "强制HTTPS",
component: {
name: "a-select",
vModel:"value",
options:[
{value:true,label:"强制HTTPS"},
{value:false,label:"不强制HTTPS"},
]
},
required: true //必填
})
forceHttps!: boolean;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cn-hangzhou',
component: {
name: 'a-select',
options: [
{ value: 'cn-hangzhou', label: '中国大陆' },
{ value: 'ap-southeast-1', label: '新加坡' },
],
},
required: true,
})
casRegion!: string;
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到云原生Api网关');
if(!this.domainList){
throw new Error('您还未选择域名');
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
let certId: any = this.cert;
if (typeof this.cert === 'object') {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
region: this.casRegion,
});
certId = await sslClient.uploadCert({
name: this.buildCertName(CertReader.getMainDomain(this.cert.crt)),
cert: this.cert,
});
}
const certIdentify = `${certId}-${this.casRegion}`
for (const domainId of this.domainList ) {
this.logger.info(`[${domainId}]开始部署`)
await this.updateCert(client, domainId,certIdentify);
this.logger.info(`[${domainId}]部署成功`)
}
this.logger.info('部署完成');
}
async updateCert(client: any, domainId: string,certIdentify:string) {
const domainInfoRes = await client.doRequest({
action: "GetDomain",
version: "2024-03-27",
protocol: "HTTPS",
method: "GET",
authType: "AK",
style: "ROA",
pathname: `/v1/domains/${domainId}`,
});
const tlsCipherSuitesConfig = domainInfoRes.data?.tlsCipherSuitesConfig
const ret = await client.doRequest({
action: "UpdateDomain",
version: "2024-03-27",
method: "PUT",
style: "ROA",
pathname: `/v1/domains/${domainId}`,
data:{
body:{
certIdentifier: certIdentify,
protocol: "HTTPS",
forceHttps:this.forceHttps,
tlsCipherSuitesConfig
}
}
})
this.logger.info(`设置${domainId}证书成功:`, ret.requestId);
}
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
if (!this.regionEndpoint) {
throw new Error('请选择区域');
}
if (!this.gatewayType) {
throw new Error('请选择网关类型');
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const res =await client.doRequest({
action: "ListDomains",
version: "2024-03-27",
method: "GET",
style: "ROA",
pathname: `/v1/domains`,
data:{
query:{
pageSize: 100,
gatewayType: this.gatewayType ,
}
}
})
const list = res?.data?.items;
if (!list || list.length === 0) {
return []
}
const options = list.map((item: any) => {
return {
value: item.domainId,
label: `${item.name}<${item.domainId}>`,
domain: item.name,
};
});
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async onGetRegionList(data: any) {
const list = [
{value:"cn-qingdao",label:"华北1青岛",endpoint:"apig.cn-qingdao.aliyuncs.com"},
{value:"cn-beijing",label:"华北2北京",endpoint:"apig.cn-beijing.aliyuncs.com"},
{value:"cn-zhangjiakou",label:"华北3张家口",endpoint:"apig.cn-zhangjiakou.aliyuncs.com"},
{value:"cn-wulanchabu",label:"华北6乌兰察布",endpoint:"apig.cn-wulanchabu.aliyuncs.com"},
{value:"cn-hangzhou",label:"华东1杭州",endpoint:"apig.cn-hangzhou.aliyuncs.com"},
{value:"cn-shanghai",label:"华东2上海",endpoint:"apig.cn-shanghai.aliyuncs.com"},
{value:"cn-shenzhen",label:"华南1深圳",endpoint:"apig.cn-shenzhen.aliyuncs.com"},
{value:"cn-heyuan",label:"华南2河源",endpoint:"apig.cn-heyuan.aliyuncs.com"},
{value:"cn-guangzhou",label:"华南3广州",endpoint:"apig.cn-guangzhou.aliyuncs.com"},
{value:"ap-southeast-2",label:"澳大利亚(悉尼)已关停",endpoint:"apig.ap-southeast-2.aliyuncs.com"},
{value:"ap-southeast-6",label:"菲律宾(马尼拉)",endpoint:"apig.ap-southeast-6.aliyuncs.com"},
{value:"ap-northeast-2",label:"韩国(首尔)",endpoint:"apig.ap-northeast-2.aliyuncs.com"},
{value:"ap-southeast-3",label:"马来西亚(吉隆坡)",endpoint:"apig.ap-southeast-3.aliyuncs.com"},
{value:"ap-northeast-1",label:"日本(东京)",endpoint:"apig.ap-northeast-1.aliyuncs.com"},
{value:"ap-southeast-7",label:"泰国(曼谷)",endpoint:"apig.ap-southeast-7.aliyuncs.com"},
{value:"cn-chengdu",label:"西南1成都",endpoint:"apig.cn-chengdu.aliyuncs.com"},
{value:"ap-southeast-1",label:"新加坡",endpoint:"apig.ap-southeast-1.aliyuncs.com"},
{value:"ap-southeast-5",label:"印度尼西亚(雅加达)",endpoint:"apig.ap-southeast-5.aliyuncs.com"},
{value:"cn-hongkong",label:"中国香港",endpoint:"apig.cn-hongkong.aliyuncs.com"},
{value:"eu-central-1",label:"德国(法兰克福)",endpoint:"apig.eu-central-1.aliyuncs.com"},
{value:"us-east-1",label:"美国(弗吉尼亚)",endpoint:"apig.us-east-1.aliyuncs.com"},
{value:"us-west-1",label:"美国(硅谷)",endpoint:"apig.us-west-1.aliyuncs.com"},
{value:"eu-west-1",label:"英国(伦敦)",endpoint:"apig.eu-west-1.aliyuncs.com"},
{value:"me-east-1",label:"阿联酋(迪拜)",endpoint:"apig.me-east-1.aliyuncs.com"},
{value:"me-central-1",label:"沙特(利雅得)",endpoint:"apig.me-central-1.aliyuncs.com"},
]
return list.map((item: any) => {
return {
value: item.endpoint,
label: item.label,
endpoint: item.endpoint,
regionId : item.value
};
})
}
}
new DeployCertToAliyunApig();

View File

@@ -10,3 +10,4 @@ export * from './deploy-to-fc/index.js';
export * from './deploy-to-esa/index.js';
export * from './deploy-to-vod/index.js';
export * from './deploy-to-apigateway/index.js';
export * from './deploy-to-apig/index.js';

View File

@@ -14,8 +14,9 @@ import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
*/
const regionDict = [
{ value: 'cn-hangzhou', endpoint: 'cas.aliyuncs.com', label: 'cn-hangzhou-中国大陆' },
{ value: 'eu-central-1', endpoint: 'cas.eu-central-1.aliyuncs.com', label: 'eu-central-1-德国(法兰克福)' },
{ value: 'ap-southeast-1', endpoint: 'cas.ap-southeast-1.aliyuncs.com', label: 'ap-southeast-1-新加坡(国际版选这个)' },
{ value: 'private-', endpoint: '', disabled:true, label: '以下是私有证书区域' },
{ value: 'eu-central-1', endpoint: 'cas.eu-central-1.aliyuncs.com', label: 'eu-central-1-德国(法兰克福)' },
{ value: 'ap-southeast-3', endpoint: 'cas.ap-southeast-3.aliyuncs.com', label: 'ap-southeast-3-马来西亚(吉隆坡)' },
{ value: 'ap-southeast-5', endpoint: 'cas.ap-southeast-5.aliyuncs.com', label: 'ap-southeast-5-印度尼西亚(雅加达)' },
{ value: 'cn-hongkong', endpoint: 'cas.cn-hongkong.aliyuncs.com', label: 'cn-hongkong-中国香港' },

View File

@@ -8,7 +8,7 @@ import {CertInfo, CertReader} from "@certd/plugin-cert";
name: "apisix",
title: "APISIX授权",
desc: "",
icon: "svg:icon-ksyun"
icon: "svg:icon-lucky"
})
export class ApisixAccess extends BaseAccess {
@@ -93,7 +93,7 @@ export class ApisixAccess extends BaseAccess {
headers,
baseURL: this.endpoint,
...req,
logRes: true,
logRes: false,
});
}

View File

@@ -0,0 +1,107 @@
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { HttpRequestConfig } from "@certd/basic";
import { CertInfo } from "@certd/plugin-cert";
/**
*/
@IsAccess({
name: "dokploy",
title: "Dokploy授权",
desc: "",
icon: "svg:icon-lucky"
})
export class DokployAccess extends BaseAccess {
@AccessInput({
title: "Dokploy地址",
component: {
placeholder: "http://192.168.11.11:5480",
},
required: true,
})
endpoint = '';
@AccessInput({
title: 'ApiKey',
component: {
placeholder: 'ApiKey',
},
// naAyXbZmxtsfrDfneOCeirbQNIICmBgfBiYXQwryPIUOdzPkXkfnaKjeAdbOQdwp
//tlyvdNzojaFkNfGScALLmyuFHkHcYWaxoYjiDzWFHcnZAWdjOquMSqBwHLvGDGZK
helper: "[settings-profile](https://app.dokploy.com/dashboard/settings/profile)中配置API Keys",
required: true,
encrypt: true,
})
apiKey = '';
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
async onTestRequest() {
await this.getCertList();
return "ok"
}
async getCertList(){
const req = {
url :"/api/certificates.all",
method: "get",
}
return await this.doRequest(req);
}
async createCert(opts:{cert:CertInfo,serverId:string,name:string}){
const req = {
url :"/api/certificates.create",
method: "post",
data:{
// certificateId:opts.certificateId,
"name": opts.name,
"certificateData": opts.cert.crt,
"privateKey": opts.cert.key,
"serverId": opts.serverId,
autoRenew: false,
organizationId : ""
}
}
return await this.doRequest(req);
}
async removeCert (opts:{id:string}){
const req = {
url :"/api/certificates.remove",
method: "post",
data:{
certificateId:opts.id,
}
}
return await this.doRequest(req);
}
async doRequest(req: HttpRequestConfig){
const headers = {
"x-api-key": this.apiKey,
...req.headers
};
return await this.ctx.http.request({
headers,
baseURL: this.endpoint,
...req,
logRes: true,
});
}
}
new DokployAccess();

View File

@@ -0,0 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";

View File

@@ -0,0 +1 @@
import "./plugin-refresh-cert.js"

View File

@@ -0,0 +1,117 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert";
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
import {DokployAccess} from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能就是目录plugin-demo中的demo大写字母开头驼峰命名
name: "DokployRefreshCert",
title: "Dokploy-更新证书",
desc: "自动更新Dokploy证书",
icon: "svg:icon-lucky",
//插件分组
group: pluginGroups.panel.key,
needPlus: true,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
//类名规范跟上面插件名称name一致
export class DokployRefreshCert extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
//授权选择框
@TaskInput({
title: "Dokploy授权",
component: {
name: "access-selector",
type: "dokploy" //固定授权类型
},
required: true //必填
})
accessId!: string;
//
@TaskInput(
createRemoteSelectInputDefine({
title: "证书名称",
helper: "要更新的证书名称,如果这里没有,请先给手动绑定一次证书",
action: DokployRefreshCert.prototype.onGetCertList.name,
pager: false,
search: false
})
)
certList!: string[];
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<DokployAccess>(this.accessId);
// await access.createCert({cert:this.cert})
const certList = await access.getCertList();
for (const certId of this.certList) {
this.logger.info(`----------- 开始更新证书:${certId}`);
const [serverId,name] = certId.split("#");
const founds = certList.filter((item: any) => item.name === name);
if (founds){
for (const found of founds) {
await access.removeCert({id:found.certificateId})
}
}
await access.createCert({
name,
cert: this.cert,
serverId: serverId,
});
this.logger.info(`----------- 更新证书${certId}成功`);
}
this.logger.info("部署完成");
}
async onGetCertList(data: PageSearch = {}) {
const access = await this.getAccess<DokployAccess>(this.accessId);
const res = await access.getCertList()
const list = res
if (!list || list.length === 0) {
throw new Error("没有找到证书你可以直接手动输入id如果id不存在将自动创建");
}
const options = list.map((item: any) => {
return {
label: `${item.name}<${item.serverId}>`,
value: `${item.serverId}#${item.name}`,
domain: item.name
};
});
return options;
}
}
//实例化一下,注册插件
new DokployRefreshCert();

View File

@@ -61,10 +61,12 @@ export class ProxmoxAccess extends BaseAccess {
@AccessInput({
title: '领域',
value: "pam",
component: {
placeholder: 'realm',
placeholder: 'pam、pve。默认值 pam',
},
required: true,
helper:"pam 或 pve。默认值 pam",
required: false,
encrypt: false,
})
realm = '';

View File

@@ -71,8 +71,14 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
for (const node of this.nodes) {
this.logger.info(`开始上传证书到节点:${node}`);
const res = await client.nodes.get(node).certificates.custom.uploadCustomCert(cert.crt, true, cert.key, true);
this.logger.info(`上传结果:${JSON.stringify(res.response)}`);
try{
const res = await client.nodes.get(node).certificates.custom.uploadCustomCert(cert.crt, true, cert.key, true);
this.logger.info(`上传结果:${JSON.stringify(res.response)}`);
}catch (e) {
this.logger.error(`执行失败:${e.message},请检查节点名称是否正确`);
throw e
}
}
this.logger.info('部署成功');

View File

@@ -34,20 +34,6 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
})
ingressClass!: string;
/**
* AccessProvider的key,或者一个包含access的具体的对象
*/
@TaskInput({
title: "Access授权",
helper: "access授权",
component: {
name: "access-selector",
type: "tencent"
},
required: true
})
accessId!: string;
@TaskInput({
title: "腾讯云证书id",
helper: "请选择“上传证书到腾讯云”前置任务的输出",
@@ -66,6 +52,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
})
tencentCertId!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
@@ -85,6 +72,24 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
cert!: any;
/**
* AccessProvider的key,或者一个包含access的具体的对象
*/
@TaskInput({
title: "Access授权",
helper: "access授权",
component: {
name: "access-selector",
type: "tencent"
},
required: true
})
accessId!: string;
@TaskInput({ title: "大区", value: "ap-guangzhou", required: true })
region!: string;
@@ -147,6 +152,17 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
})
skipTLSVerify!:boolean
@TaskInput({
title: "Secret自动创建",
helper: "如果Secret不存在则创建",
value: false,
component: {
name: "a-switch",
vModel: "checked",
},
})
createOnNotFound: boolean;
// @TaskInput({ title: "集群内网ip", helper: "如果开启了外网的话,无需设置" })
// clusterIp!: string;
@@ -288,7 +304,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
secretNames = [secretName];
}
for (const secret of secretNames) {
await k8sClient.patchSecret({ namespace, secretName: secret, body });
await k8sClient.patchSecret({ namespace, secretName: secret, body , createOnNotFound: this.createOnNotFound});
this.logger.info(`CertSecret已更新:${secret}`);
}
}

16
pnpm-lock.yaml generated
View File

@@ -1492,8 +1492,8 @@ importers:
specifier: ^1.36.17
version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.1
version: 8.4.1
specifier: ^8.4.2
version: 8.4.2
'@certd/jdcloud':
specifier: ^1.36.17
version: link:../../libs/lib-jdcloud
@@ -2766,8 +2766,8 @@ packages:
'@better-scroll/zoom@2.5.1':
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
'@certd/cv4pve-api-javascript@8.4.1':
resolution: {integrity: sha512-jxlRieJmCA0Z9LnwX6Ra6ZekRGJEu8o8RGYoKU0Jjkhc9jm6ChEbVyfE7Iw49/hlpA+2yaHdAXb46au/afCISg==}
'@certd/cv4pve-api-javascript@8.4.2':
resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==}
'@certd/vue-js-cron-core@6.0.3':
resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==}
@@ -15448,7 +15448,7 @@ snapshots:
dependencies:
'@better-scroll/core': 2.5.1
'@certd/cv4pve-api-javascript@8.4.1':
'@certd/cv4pve-api-javascript@8.4.2':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
@@ -21535,13 +21535,13 @@ snapshots:
resolve: 1.22.10
semver: 6.3.1
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8):
dependencies:
eslint: 7.32.0
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
optionalDependencies:
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-config-prettier: 8.10.0(eslint@8.57.0)
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
dependencies:
@@ -24249,7 +24249,7 @@ snapshots:
eslint: 7.32.0
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-node: 11.1.0(eslint@7.32.0)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8)
execa: 5.1.1
inquirer: 7.3.3
json5: 2.2.3