Merge branch 'v2-dev' into v2

pull/436/head
xiaojunnuo 2025-06-05 23:56:47 +08:00
commit 1bbed351ba
84 changed files with 1012 additions and 296 deletions

View File

@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复用户最大流水线数量校验的问题 ([919f70a](https://github.com/certd/certd/commit/919f70a5fd2842ca69f96f1659bb5a7ba3f73776))
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
* 修复cv4pve sdk (proxmox插件连接失败时无法正常结束任务的bug) ([49f26b4](https://github.com/certd/certd/commit/49f26b4049a0549b0270395157e96e8f04a68bc4))
* 修复flexcdn部署证书的顶级CA名称显示 ([6467edb](https://github.com/certd/certd/commit/6467edb84324d7c80a85212675dbacedc459df83))
* 修复flexcdn证书commonNames错误的问题 ([ace363f](https://github.com/certd/certd/commit/ace363fa355436e769b27f71cc487d30d6441780))
### Performance Improvements
* 分组选择支持清空选项 ([03e2e99](https://github.com/certd/certd/commit/03e2e9949837b34eb3ea56d14a9e8a5dabc96063))
* 优化cname检查当有冲突的cname记录时给出提示 ([e639a8f](https://github.com/certd/certd/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
* 增加下载日志按钮 ([6ff509d](https://github.com/certd/certd/commit/6ff509d263c0182645b4692c10b5fedb192db964))
* 站点监控支持批量导入域名和ip ([2d7729d](https://github.com/certd/certd/commit/2d7729dbe98f29088f5f317db2b52cc1ede223a6))
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Bug Fixes ### Bug Fixes

View File

@ -168,7 +168,11 @@ https://afdian.com/a/greper
1. 可以调整开源协议以使其更严格或更宽松。 1. 可以调整开源协议以使其更严格或更宽松。
2. 可以用于商业用途。 2. 可以用于商业用途。
感谢以下贡献者做出的贡献。
<a href="https://github.com/certd/certd/graphs/contributors">
<img src="https://contrib.rocks/image?repo=certd/certd" />
</a>
## 九、 开源许可 ## 九、 开源许可
* 本项目遵循 GNU Affero General Public LicenseAGPL开源协议。 * 本项目遵循 GNU Affero General Public LicenseAGPL开源协议。

View File

@ -1 +1 @@
23:57 23:51

View File

@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复用户最大流水线数量校验的问题 ([919f70a](https://github.com/certd/certd/commit/919f70a5fd2842ca69f96f1659bb5a7ba3f73776))
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
* 修复cv4pve sdk (proxmox插件连接失败时无法正常结束任务的bug) ([49f26b4](https://github.com/certd/certd/commit/49f26b4049a0549b0270395157e96e8f04a68bc4))
* 修复flexcdn部署证书的顶级CA名称显示 ([6467edb](https://github.com/certd/certd/commit/6467edb84324d7c80a85212675dbacedc459df83))
* 修复flexcdn证书commonNames错误的问题 ([ace363f](https://github.com/certd/certd/commit/ace363fa355436e769b27f71cc487d30d6441780))
### Performance Improvements
* 分组选择支持清空选项 ([03e2e99](https://github.com/certd/certd/commit/03e2e9949837b34eb3ea56d14a9e8a5dabc96063))
* 优化cname检查当有冲突的cname记录时给出提示 ([e639a8f](https://github.com/certd/certd/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
* 增加下载日志按钮 ([6ff509d](https://github.com/certd/certd/commit/6ff509d263c0182645b4692c10b5fedb192db964))
* 站点监控支持批量导入域名和ip ([2d7729d](https://github.com/certd/certd/commit/2d7729dbe98f29088f5f317db2b52cc1ede223a6))
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Bug Fixes ### Bug Fixes

View File

@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.34.10" "version": "1.34.11"
} }

View File

@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/publishlab/node-acme-client/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/publishlab/node-acme-client/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
### Performance Improvements
* 优化cname检查当有冲突的cname记录时给出提示 ([e639a8f](https://github.com/publishlab/node-acme-client/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
## [1.34.10](https://github.com/publishlab/node-acme-client/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/publishlab/node-acme-client/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/acme-client **Note:** Version bump only for package @certd/acme-client

View File

@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.34.10", "version": "1.34.11",
"type": "module", "type": "module",
"module": "scr/index.js", "module": "scr/index.js",
"main": "src/index.js", "main": "src/index.js",
@ -18,7 +18,7 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"@certd/basic": "^1.34.10", "@certd/basic": "^1.34.11",
"@peculiar/x509": "^1.11.0", "@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"axios": "^1.7.2", "axios": "^1.7.2",
@ -27,7 +27,7 @@
"https-proxy-agent": "^7.0.5", "https-proxy-agent": "^7.0.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"punycode": "^2.3.1" "punycode.js": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.10", "@types/node": "^20.14.10",
@ -69,5 +69,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -203,6 +203,7 @@ export const agents: any;
export function setLogger(fn: (message: any, ...args: any[]) => void): void; export function setLogger(fn: (message: any, ...args: any[]) => void): void;
export function walkTxtRecord(record: any): Promise<string[]>; export function walkTxtRecord(record: any): Promise<string[]>;
export function getAuthoritativeDnsResolver(record:string): Promise<any>;
export const CancelError: typeof CancelError; export const CancelError: typeof CancelError;

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/basic
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Performance Improvements ### Performance Improvements

View File

@ -1 +1 @@
23:54 23:48

View File

@ -1,7 +1,7 @@
{ {
"name": "@certd/basic", "name": "@certd/basic",
"private": false, "private": false,
"version": "1.34.10", "version": "1.34.11",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@ -45,5 +45,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/pipeline
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Performance Improvements ### Performance Improvements

View File

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

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/lib-huawei
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/lib-huawei **Note:** Version bump only for package @certd/lib-huawei

View File

@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-huawei", "name": "@certd/lib-huawei",
"private": false, "private": false,
"version": "1.34.10", "version": "1.34.11",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts", "types": "./dist/d/index.d.ts",
@ -24,5 +24,5 @@
"prettier": "^2.8.8", "prettier": "^2.8.8",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/lib-iframe
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/lib-iframe **Note:** Version bump only for package @certd/lib-iframe

View File

@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-iframe", "name": "@certd/lib-iframe",
"private": false, "private": false,
"version": "1.34.10", "version": "1.34.11",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@ -31,5 +31,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/jdcloud
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/jdcloud **Note:** Version bump only for package @certd/jdcloud

View File

@ -1,6 +1,6 @@
{ {
"name": "@certd/jdcloud", "name": "@certd/jdcloud",
"version": "1.34.10", "version": "1.34.11",
"description": "jdcloud openApi sdk", "description": "jdcloud openApi sdk",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
@ -61,5 +61,5 @@
"fetch" "fetch"
] ]
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/lib-k8s
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/lib-k8s **Note:** Version bump only for package @certd/lib-k8s

View File

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

View File

@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Performance Improvements
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/lib-server **Note:** Version bump only for package @certd/lib-server

View File

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

View File

@ -16,6 +16,7 @@ export class SysPublicSettings extends BaseSettings {
static __access__ = 'public'; static __access__ = 'public';
registerEnabled = false; registerEnabled = false;
userValidTimeEnabled?:boolean = false;
passwordLoginEnabled = true; passwordLoginEnabled = true;
usernameRegisterEnabled = true; usernameRegisterEnabled = true;
mobileRegisterEnabled = false; mobileRegisterEnabled = false;

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/midway-flyway-js **Note:** Version bump only for package @certd/midway-flyway-js

View File

@ -1,6 +1,6 @@
{ {
"name": "@certd/midway-flyway-js", "name": "@certd/midway-flyway-js",
"version": "1.34.10", "version": "1.34.11",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@ -46,5 +46,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
**Note:** Version bump only for package @certd/plugin-cert **Note:** Version bump only for package @certd/plugin-cert

View File

@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-cert", "name": "@certd/plugin-cert",
"private": false, "private": false,
"version": "1.34.10", "version": "1.34.11",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@ -16,16 +16,16 @@
"pub": "npm publish" "pub": "npm publish"
}, },
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.34.10", "@certd/acme-client": "^1.34.11",
"@certd/basic": "^1.34.10", "@certd/basic": "^1.34.11",
"@certd/pipeline": "^1.34.10", "@certd/pipeline": "^1.34.11",
"@certd/plugin-lib": "^1.34.10", "@certd/plugin-lib": "^1.34.11",
"@google-cloud/publicca": "^1.3.0", "@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"psl": "^1.9.0", "psl": "^1.9.0",
"punycode": "^2.3.1", "punycode.js": "^2.3.1",
"rimraf": "^5.0.5" "rimraf": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
@ -43,5 +43,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a4b6580247efabe948507c771a177d4f75670bc2" "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
} }

View File

@ -29,9 +29,25 @@ export type DnsProviderContext = {
export interface IDnsProvider<T = any> { export interface IDnsProvider<T = any> {
onInstance(): Promise<void>; onInstance(): Promise<void>;
/**
*
* @param domain
*/
punyCodeEncode(domain: string): string;
/**
*
* @param domain
*/
punyCodeDecode(domain: string): string;
createRecord(options: CreateRecordOptions): Promise<T>; createRecord(options: CreateRecordOptions): Promise<T>;
removeRecord(options: RemoveRecordOptions<T>): Promise<void>; removeRecord(options: RemoveRecordOptions<T>): Promise<void>;
setCtx(ctx: DnsProviderContext): void; setCtx(ctx: DnsProviderContext): void;
//中文域名是否需要punycode转码如果返回True则使用punycode来添加解析记录否则使用中文域名添加解析记录 //中文域名是否需要punycode转码如果返回True则使用punycode来添加解析记录否则使用中文域名添加解析记录
usePunyCode(): boolean; usePunyCode(): boolean;
} }

View File

@ -1,7 +1,7 @@
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, IDnsProvider, RemoveRecordOptions } from "./api.js"; import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, IDnsProvider, RemoveRecordOptions } from "./api.js";
import { dnsProviderRegistry } from "./registry.js"; import { dnsProviderRegistry } from "./registry.js";
import { HttpClient, ILogger } from "@certd/basic"; import { HttpClient, ILogger } from "@certd/basic";
import punycode from "punycode.js";
export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> { export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
ctx!: DnsProviderContext; ctx!: DnsProviderContext;
http!: HttpClient; http!: HttpClient;
@ -13,6 +13,22 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
return false; return false;
} }
/**
*
* @param domain
*/
punyCodeEncode(domain: string) {
return punycode.toASCII(domain);
}
/**
*
* @param domain
*/
punyCodeDecode(domain: string) {
return punycode.toUnicode(domain);
}
setCtx(ctx: DnsProviderContext) { setCtx(ctx: DnsProviderContext) {
this.ctx = ctx; this.ctx = ctx;
this.logger = ctx.logger; this.logger = ctx.logger;

View File

@ -6,7 +6,7 @@ import { Challenge } from "@certd/acme-client/types/rfc8555";
import { IContext } from "@certd/pipeline"; import { IContext } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic"; import { ILogger, utils } from "@certd/basic";
import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js"; import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js";
import punycode from "node:punycode"; import punycode from "punycode.js";
import { IOssClient } from "@certd/plugin-lib"; import { IOssClient } from "@certd/plugin-lib";
export type CnameVerifyPlan = { export type CnameVerifyPlan = {
type?: string; type?: string;
@ -233,16 +233,18 @@ export class AcmeService {
let dnsProvider = providers.dnsProvider; let dnsProvider = providers.dnsProvider;
let fullRecord = `_acme-challenge.${fullDomain}`; let fullRecord = `_acme-challenge.${fullDomain}`;
const origDomain = punycode.toUnicode(domain);
const origFullDomain = punycode.toUnicode(fullDomain);
if (providers.domainsVerifyPlan) { if (providers.domainsVerifyPlan) {
//按照计划执行 //按照计划执行
const domainVerifyPlan = providers.domainsVerifyPlan[domain]; const domainVerifyPlan = providers.domainsVerifyPlan[origDomain];
if (domainVerifyPlan) { if (domainVerifyPlan) {
if (domainVerifyPlan.type === "dns") { if (domainVerifyPlan.type === "dns") {
dnsProvider = domainVerifyPlan.dnsProvider; dnsProvider = domainVerifyPlan.dnsProvider;
} else if (domainVerifyPlan.type === "cname") { } else if (domainVerifyPlan.type === "cname") {
const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan; const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
if (cnameVerifyPlan) { if (cnameVerifyPlan) {
const cname = cnameVerifyPlan[fullDomain]; const cname = cnameVerifyPlan[origFullDomain];
if (cname) { if (cname) {
dnsProvider = cname.dnsProvider; dnsProvider = cname.dnsProvider;
domain = await this.options.domainParser.parse(cname.domain); domain = await this.options.domainParser.parse(cname.domain);

View File

@ -5,6 +5,7 @@ import path from "path";
import { CertificateInfo, crypto } from "@certd/acme-client"; import { CertificateInfo, crypto } from "@certd/acme-client";
import { ILogger } from "@certd/basic"; import { ILogger } from "@certd/basic";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { uniq } from "lodash-es";
export type CertReaderHandleContext = { export type CertReaderHandleContext = {
reader: CertReader; reader: CertReader;
@ -90,7 +91,8 @@ export class CertReader {
const { detail } = this.getCrtDetail(); const { detail } = this.getCrtDetail();
const domains = [detail.domains.commonName]; const domains = [detail.domains.commonName];
domains.push(...detail.domains.altNames); domains.push(...detail.domains.altNames);
return domains; //去重
return uniq(domains);
} }
getAltNames() { getAltNames() {

View File

@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
**Note:** Version bump only for package @certd/plugin-lib
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Performance Improvements ### Performance Improvements

View File

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

View File

@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
* 修复flexcdn部署证书的顶级CA名称显示 ([6467edb](https://github.com/certd/certd/commit/6467edb84324d7c80a85212675dbacedc459df83))
### Performance Improvements
* 分组选择支持清空选项 ([03e2e99](https://github.com/certd/certd/commit/03e2e9949837b34eb3ea56d14a9e8a5dabc96063))
* 优化cname检查当有冲突的cname记录时给出提示 ([e639a8f](https://github.com/certd/certd/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
* 增加下载日志按钮 ([6ff509d](https://github.com/certd/certd/commit/6ff509d263c0182645b4692c10b5fedb192db964))
* 站点监控支持批量导入域名和ip ([2d7729d](https://github.com/certd/certd/commit/2d7729dbe98f29088f5f317db2b52cc1ede223a6))
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Performance Improvements ### Performance Improvements

View File

@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-client", "name": "@certd/ui-client",
"version": "1.34.10", "version": "1.34.11",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite --open",
@ -9,7 +9,7 @@
"debug": "vite --mode debug --open", "debug": "vite --mode debug --open",
"debug:pm": "vite --mode debugpm", "debug:pm": "vite --mode debugpm",
"debug:force": "vite --force --mode debug", "debug:force": "vite --force --mode debug",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=32768 vite build ", "build": "cross-env NODE_OPTIONS=--max-old-space-size=40960 vite build ",
"dev-build": "echo 1", "dev-build": "echo 1",
"test:unit": "vitest", "test:unit": "vitest",
"serve": "vite preview", "serve": "vite preview",
@ -102,8 +102,8 @@
"zod-defaults": "^0.1.3" "zod-defaults": "^0.1.3"
}, },
"devDependencies": { "devDependencies": {
"@certd/lib-iframe": "^1.34.10", "@certd/lib-iframe": "^1.34.11",
"@certd/pipeline": "^1.34.10", "@certd/pipeline": "^1.34.11",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12", "@types/chai": "^4.3.12",

View File

@ -15,6 +15,7 @@ import PemInput from "./pem-input.vue";
import { defineAsyncComponent } from "vue"; import { defineAsyncComponent } from "vue";
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue"; import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
import EmailSelector from "./email-selector/index.vue"; import EmailSelector from "./email-selector/index.vue";
import ValidTimeFormat from "./valid-time-format.vue";
export default { export default {
install(app: any) { install(app: any) {
app.component( app.component(
@ -27,6 +28,7 @@ export default {
app.component("TextEditable", TextEditable); app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput); app.component("FileInput", FileInput);
app.component("PemInput", PemInput); app.component("PemInput", PemInput);
app.component("ValidTimeFormat", ValidTimeFormat);
// app.component("CodeEditor", CodeEditor); // app.component("CodeEditor", CodeEditor);
app.component("CronLight", CronLight); app.component("CronLight", CronLight);

View File

@ -8,6 +8,7 @@ export type CnameRecord = {
status?: string; status?: string;
hostRecord?: string; hostRecord?: string;
recordValue?: string; recordValue?: string;
error?: string;
}; };
export type DomainGroupItem = { export type DomainGroupItem = {

View File

@ -13,6 +13,9 @@
</td> </td>
<td class="status center flex-center"> <td class="status center flex-center">
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" /> <fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
</a-tooltip>
</td> </td>
<td class="center"> <td class="center">
<template v-if="cnameRecord.status !== 'valid'"> <template v-if="cnameRecord.status !== 'valid'">

View File

@ -21,7 +21,7 @@ import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-edit
import { computed } from "vue"; import { computed } from "vue";
defineOptions({ defineOptions({
name: "CnameVerifyPlan" name: "CnameVerifyPlan",
}); });
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);

View File

@ -55,6 +55,7 @@
</div> </div>
<div v-if="item.type === 'http'" class="plan-http"> <div v-if="item.type === 'http'" class="plan-http">
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" /> <http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
<div class="helper">证书颁发机构将请求 https://yourdomain/.well-known/acme-challenge/xxxxxx </div>
</div> </div>
</div> </div>
</td> </td>

View File

@ -0,0 +1,32 @@
<template>
<div class="valid-time-format">
<a-tag v-if="isExpired" color="red">{{ prefix || "" }}</a-tag>
<a-tag v-if="isValid" color="green" :title="date">
<fs-time-humanize v-if="humanize" :model-value="modelValue" :options="{ largest: 1, units: ['y', 'd', 'h'] }" :use-format-greater="30000000000" />
<template v-else> {{ prefix || "" }}{{ date }} </template>
</a-tag>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import dayjs from "dayjs";
const props = defineProps<{
modelValue: number;
humanize?: boolean;
prefix?: string;
}>();
const date = computed(() => {
return dayjs(props.modelValue || 0).format("YYYY-MM-DD");
});
const isValid = computed(() => {
return props.modelValue > 0 && props.modelValue > new Date().getTime();
});
const isExpired = computed(() => {
return props.modelValue > 0 && props.modelValue < new Date().getTime();
});
</script>

View File

@ -233,13 +233,13 @@ function openUpgrade() {
desc: "社区免费版", desc: "社区免费版",
type: "free", type: "free",
icon: "lucide:package-open", icon: "lucide:package-open",
privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件", "邮件、webhook通知方式"], privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn、宝塔、1Panel等部署插件", "邮件、webhook通知方式"],
}, },
plus: { plus: {
title: "专业版", title: "专业版",
desc: "开源需要您的赞助支持", desc: "开源需要您的赞助支持",
type: "plus", type: "plus",
privilege: ["可加VIP群您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,更多强大的部署插件宝塔、群晖、1Panel等"], privilege: ["可加VIP群您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,群辉等更多插件"],
trial: { trial: {
title: "点击获取7天试用", title: "点击获取7天试用",
click: () => { click: () => {

View File

@ -120,10 +120,10 @@ function install(app: App, options: any = {}) {
rowHandle: { rowHandle: {
fixed: "right", fixed: "right",
buttons: { buttons: {
view: { type: "link", text: null, icon: "ion:eye-outline" }, view: { type: "link", text: null, icon: "ion:eye-outline", tooltip: { title: "查看" } },
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" }, copy: { show: true, type: "link", text: null, icon: "ion:copy-outline", tooltip: { title: "复制" } },
edit: { type: "link", text: null, icon: "ion:create-outline" }, edit: { type: "link", text: null, icon: "ion:create-outline", tooltip: { title: "编辑" } },
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" }, remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline", tooltip: { title: "删除" } },
}, },
dropdown: { dropdown: {
more: { more: {

View File

@ -122,7 +122,7 @@ export const certdResources = [
}, },
{ {
title: "OpenKey", title: "开放接口密钥",
name: "OpenKey", name: "OpenKey",
path: "/certd/open/openkey", path: "/certd/open/openkey",
component: "/certd/open/openkey/index.vue", component: "/certd/open/openkey/index.vue",

View File

@ -30,6 +30,7 @@ export type PlusInfo = {
}; };
export type SysPublicSetting = { export type SysPublicSetting = {
registerEnabled?: boolean; registerEnabled?: boolean;
userValidTimeEnabled?: boolean;
usernameRegisterEnabled?: boolean; usernameRegisterEnabled?: boolean;
mobileRegisterEnabled?: boolean; mobileRegisterEnabled?: boolean;
emailRegisterEnabled?: boolean; emailRegisterEnabled?: boolean;

View File

@ -27,6 +27,8 @@ export interface UserInfoRes {
avatar?: string; avatar?: string;
roleIds: number[]; roleIds: number[];
isWeak?: boolean; isWeak?: boolean;
validTime?: number;
status?: number;
} }
export interface LoginRes { export interface LoginRes {

View File

@ -2,7 +2,7 @@ import * as api from "./api";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Ref, ref } from "vue"; import { Ref, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
@ -31,7 +31,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]); const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys; context.selectedRowKeys = selectedRowKeys;
const dictRef = dict({
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" },
],
});
return { return {
crudOptions: { crudOptions: {
settings: { settings: {
@ -174,21 +182,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
status: { status: {
title: "状态", title: "状态",
type: "dict-select", type: "dict-select",
dict: dict({ dict: dictRef,
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" },
],
}),
addForm: { addForm: {
show: false, show: false,
}, },
column: { column: {
width: 120, width: 120,
align: "center", align: "center",
cellRender({ value, row }) {
return (
<div class={"flex flex-center"}>
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
{row.error && (
<a-tooltip title={row.error}>
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
</a-tooltip>
)}
</div>
);
},
}, },
}, },
triggerValidate: { triggerValidate: {

View File

@ -6,7 +6,7 @@ export async function GetList(query: any) {
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",
method: "post", method: "post",
data: query data: query,
}); });
} }
@ -14,7 +14,7 @@ export async function AddObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/add", url: apiPrefix + "/add",
method: "post", method: "post",
data: obj data: obj,
}); });
} }
@ -22,7 +22,7 @@ export async function UpdateObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/update", url: apiPrefix + "/update",
method: "post", method: "post",
data: obj data: obj,
}); });
} }
@ -30,7 +30,7 @@ export async function DelObj(id: any) {
return await request({ return await request({
url: apiPrefix + "/delete", url: apiPrefix + "/delete",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@ -38,7 +38,7 @@ export async function GetObj(id: any) {
return await request({ return await request({
url: apiPrefix + "/info", url: apiPrefix + "/info",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@ -46,7 +46,7 @@ export async function GetDetail(id: any) {
return await request({ return await request({
url: apiPrefix + "/detail", url: apiPrefix + "/detail",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@ -54,6 +54,6 @@ export async function DeleteBatch(ids: any[]) {
return await request({ return await request({
url: apiPrefix + "/deleteByIds", url: apiPrefix + "/deleteByIds",
method: "post", method: "post",
data: { ids } data: { ids },
}); });
} }

View File

@ -56,6 +56,14 @@ export const siteInfoApi = {
}); });
}, },
async Import(form: any) {
return await request({
url: apiPrefix + "/import",
method: "post",
data: form,
});
},
async DisabledChange(id: number, disabled: boolean) { async DisabledChange(id: number, disabled: boolean) {
return await request({ return await request({
url: apiPrefix + "/disabledChange", url: apiPrefix + "/disabledChange",

View File

@ -8,6 +8,7 @@ import { useSettingStore } from "/@/store/settings";
import { mySuiteApi } from "/@/views/certd/suite/mine/api"; import { mySuiteApi } from "/@/views/certd/suite/mine/api";
import { mitter } from "/@/utils/util.mitt"; import { mitter } from "/@/utils/util.mitt";
import { useSiteIpMonitor } from "./ip/use"; import { useSiteIpMonitor } from "./ip/use";
import { useSiteImport } from "/@/views/certd/monitor/site/use";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@ -44,6 +45,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}); });
const { openSiteIpMonitorDialog } = useSiteIpMonitor(); const { openSiteIpMonitorDialog } = useSiteIpMonitor();
const { openSiteImportDialog } = useSiteImport();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@ -97,6 +99,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
await crudExpose.openAdd({}); await crudExpose.openAdd({});
}, },
}, },
//导入按钮
import: {
show: true,
text: "批量导入",
type: "primary",
async click() {
openSiteImportDialog({
afterSubmit() {
crudExpose.doRefresh();
},
});
},
},
}, },
}, },
rowHandle: { rowHandle: {
@ -235,7 +250,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}; };
return ( return (
<a-popover placement="left" v-slots={slots} overlayStyle={{ maxWidth: "30%" }}> <a-popover placement={"left"} v-slots={slots} overlayStyle={{ maxWidth: "30%" }}>
{row.certDomains} {row.certDomains}
</a-popover> </a-popover>
); );

View File

@ -68,4 +68,11 @@ export const siteIpApi = {
}, },
}); });
}, },
async Import(form: any) {
return await request({
url: apiPrefix + "/import",
method: "post",
data: form,
});
},
}; };

View File

@ -4,13 +4,11 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { siteIpApi } from "./api"; import { siteIpApi } from "./api";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Modal, notification } from "ant-design-vue"; import { Modal, notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/settings"; import { useSiteIpMonitor } from "/@/views/certd/monitor/site/ip/use";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = siteIpApi; const api = siteIpApi;
const { crudBinding } = crudExpose;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
if (!query.query) { if (!query.query) {
query.query = {}; query.query = {};
@ -36,8 +34,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res; return res;
}; };
const settingsStore = useSettingStore();
const checkStatusDict = dict({ const checkStatusDict = dict({
data: [ data: [
{ label: "成功", value: "ok", color: "green" }, { label: "成功", value: "ok", color: "green" },
@ -45,6 +41,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{ label: "异常", value: "error", color: "red" }, { label: "异常", value: "error", color: "red" },
], ],
}); });
const { openSiteIpImportDialog } = useSiteIpMonitor();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@ -75,6 +72,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
await crudExpose.openAdd({}); await crudExpose.openAdd({});
}, },
}, },
import: {
show: true,
text: "批量导入",
type: "primary",
async click() {
openSiteIpImportDialog({
siteId: context.props.siteId,
afterSubmit() {
crudExpose.doRefresh();
},
});
},
},
load: { load: {
text: "同步IP", text: "同步IP",
type: "primary", type: "primary",
@ -295,6 +305,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
data: [ data: [
{ label: "同步", value: "sync", color: "green" }, { label: "同步", value: "sync", color: "green" },
{ label: "手动", value: "manual", color: "blue" }, { label: "手动", value: "manual", color: "blue" },
{ label: "导入", value: "import", color: "blue" },
], ],
}), }),
form: { form: {

View File

@ -1,11 +1,10 @@
import { useFormWrapper } from "@fast-crud/fast-crud"; import { useFormWrapper } from "@fast-crud/fast-crud";
import { useRouter } from "vue-router";
import SiteIpCertMonitor from "./index.vue"; import SiteIpCertMonitor from "./index.vue";
import { siteIpApi } from "/@/views/certd/monitor/site/ip/api";
export function useSiteIpMonitor() { export function useSiteIpMonitor() {
const { openDialog } = useFormWrapper(); const { openDialog, openCrudFormDialog } = useFormWrapper();
const router = useRouter();
async function openSiteIpMonitorDialog(opts: { siteId: number }) { async function openSiteIpMonitorDialog(opts: { siteId: number }) {
await openDialog({ await openDialog({
@ -34,7 +33,42 @@ export function useSiteIpMonitor() {
}); });
} }
async function openSiteIpImportDialog(opts: { afterSubmit: any; siteId: any }) {
const { afterSubmit } = opts;
await openCrudFormDialog<any>({
crudOptions: {
columns: {
text: {
type: "textarea",
title: "IP列表",
form: {
helper: "IP或者CNAME域名一行一个",
rules: [{ required: true, message: "请输入要导入的IP或域名" }],
component: {
placeholder: "192.168.1.2\ncname.foo.com",
rows: 8,
},
col: {
span: 24,
},
},
},
},
form: {
async doSubmit({ form }) {
return siteIpApi.Import({
...form,
siteId: opts.siteId,
});
},
afterSubmit,
},
},
});
}
return { return {
openSiteIpMonitorDialog, openSiteIpMonitorDialog,
openSiteIpImportDialog,
}; };
} }

View File

@ -0,0 +1,41 @@
import { useFormWrapper } from "@fast-crud/fast-crud";
import { siteInfoApi } from "./api";
export function useSiteImport() {
const { openCrudFormDialog } = useFormWrapper();
async function openSiteImportDialog(opts: { afterSubmit: any }) {
const { afterSubmit } = opts;
await openCrudFormDialog<any>({
crudOptions: {
columns: {
text: {
type: "textarea",
title: "域名列表",
form: {
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
rules: [{ required: true, message: "请输入要导入的域名" }],
component: {
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
rows: 8,
},
col: {
span: 24,
},
},
},
},
form: {
async doSubmit({ form }) {
return siteInfoApi.Import(form);
},
afterSubmit,
},
},
});
}
return {
openSiteImportDialog,
};
}

View File

@ -223,6 +223,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
play: { play: {
order: -999, order: -999,
title: "运行流水线", title: "运行流水线",
tooltip: { title: "运行流水线" },
type: "link", type: "link",
icon: "ant-design:play-circle-outlined", icon: "ant-design:play-circle-outlined",
click({ row }) { click({ row }) {
@ -276,6 +277,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
viewCert: { viewCert: {
order: 3, order: 3,
title: "查看证书", title: "查看证书",
tooltip: { title: "查看证书" },
type: "link", type: "link",
icon: "ph:certificate", icon: "ph:certificate",
async click({ row }) { async click({ row }) {
@ -286,6 +288,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
order: 4, order: 4,
type: "link", type: "link",
title: "下载证书", title: "下载证书",
tooltip: { title: "下载证书" },
icon: "ant-design:download-outlined", icon: "ant-design:download-outlined",
async click({ row }) { async click({ row }) {
await downloadCert(row); await downloadCert(row);

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="pi-group-selector flex full-w"> <div class="pi-group-selector flex full-w">
<div class="flex-1"> <div class="flex-1">
<fs-dict-select :value="modelValue" :dict="groupDictRef" @update:value="doUpdate"></fs-dict-select> <fs-dict-select :value="modelValue" :dict="groupDictRef" :allow-clear="true" @update:value="doUpdate"></fs-dict-select>
</div> </div>
<fs-table-select <fs-table-select

View File

@ -23,14 +23,11 @@
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
<template #footer> <template #footer>
<a-tooltip title="AI分析异常"> <fs-button v-if="settingsStore.sysPublic.aiChatEnabled !== false" key="aiChat" :tooltip="{ title: 'AI分析异常' }" type="primary" icon="ion:color-wand-outline" @click="taskModal.onAiChat">AI</fs-button>
<fs-button v-if="settingsStore.sysPublic.aiChatEnabled !== false" key="aiChat" type="primary" icon="ion:color-wand-outline" @click="taskModal.onAiChat">AI</fs-button> <fs-button key="rerun" type="primary" :tooltip="{ title: '强制重新执行此步骤' }" text="重新运行" icon="icon-park-outline:replay-music" @click="triggerRun(activeKey)"></fs-button>
</a-tooltip> <fs-button key="downloadLogs" type="primary" :tooltip="{ title: '当前任务日志下载' }" icon="ion:arrow-down-circle-outline" @click="taskModal.onDownloadLogs"></fs-button>
<a-tooltip title="强制重新执行此步骤"> <fs-button key="cancel" :tooltip="{ title: '关闭窗口' }" icon="ion:close-circle-outline" @click="taskModal.onOk"></fs-button>
<fs-button key="rerun" type="primary" text="重新运行" icon="icon-park-outline:replay-music" @click="triggerRun(activeKey)"></fs-button> <!-- <fs-button key="submit" :tooltip="{ title: '关闭窗口' }" icon="ion:checkmark-circle-outline" type="primary" @click="taskModal.onOk"></fs-button>-->
</a-tooltip>
<fs-button key="cancel" icon="ion:close-circle-outline" @click="taskModal.onOk"></fs-button>
<fs-button key="submit" icon="ion:checkmark-circle-outline" type="primary" @click="taskModal.onOk"></fs-button>
</template> </template>
</a-modal> </a-modal>
</template> </template>
@ -41,6 +38,7 @@ import { RunHistory } from "../../type";
import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show.vue"; import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show.vue";
import { usePreferences } from "/@/vben/preferences"; import { usePreferences } from "/@/vben/preferences";
import { useSettingStore } from "/@/store/settings/index"; import { useSettingStore } from "/@/store/settings/index";
import { notification } from "ant-design-vue";
export default { export default {
name: "PiTaskView", name: "PiTaskView",
components: { PiStatusShow }, components: { PiStatusShow },
@ -56,6 +54,22 @@ export default {
onAiChat() { onAiChat() {
onAiChat(); onAiChat();
}, },
onDownloadLogs() {
const logs = currentHistory.value?.logs[activeKey.value];
if (!logs || logs.length === 0) {
notification.warning({
message: "没有日志",
});
return;
}
const logText = logs.join("");
const blob = new Blob([logText], { type: "text/plain;charset=utf-8" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "certd-task-log.txt";
a.click();
URL.revokeObjectURL(a.href);
},
cancelText: "关闭", cancelText: "关闭",
}); });
const { isMobile } = usePreferences(); const { isMobile } = usePreferences();

View File

@ -35,6 +35,10 @@
<a-divider type="vertical" /> <a-divider type="vertical" />
<suite-card class="m-0"></suite-card> <suite-card class="m-0"></suite-card>
</template> </template>
<template v-if="settingsStore.isPlus && settingsStore.sysPublic.userValidTimeEnabled === true && userInfo.validTime">
<a-divider type="vertical" />
<valid-time-format class="flex-o" prefix="账户有效期:" :model-value="userInfo.validTime" />
</template>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,9 @@
import * as api from "./api"; import * as api from "./api";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { Modal, notification } from "ant-design-vue"; import { Modal, notification } from "ant-design-vue";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/settings";
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@ -21,6 +23,10 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore();
const userValidTimeEnabled = compute(() => {
return settingStore.sysPublic.userValidTimeEnabled === true;
});
return { return {
crudOptions: { crudOptions: {
request: { request: {
@ -210,6 +216,43 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
width: 100, width: 100,
}, },
}, },
validTime: {
title: "有效期",
type: "date",
form: {
show: userValidTimeEnabled,
},
column: {
align: "center",
sorter: true,
width: 100,
show: userValidTimeEnabled,
cellRender({ value }) {
if (value == null || value === 0) {
return "";
}
if (value < dayjs().valueOf()) {
return <a-tag color={"red"}></a-tag>;
}
const date = dayjs(value).format("YYYY-MM-DD");
return (
<a-tag color={"green"} title={date}>
<fs-time-humanize modelValue={value} options={{ largest: 1, units: ["y", "d", "h"] }} useFormatGreater={30000000000} />
</a-tag>
);
},
},
valueBuilder({ value, row, key }) {
if (value != null) {
row[key] = dayjs(value);
}
},
valueResolve({ value, row, key }) {
if (value != null) {
row[key] = value.valueOf();
}
},
},
remark: { remark: {
title: "备注", title: "备注",
type: "text", type: "text",

View File

@ -11,6 +11,13 @@
<a-form-item label="开启自助注册" :name="['public', 'registerEnabled']"> <a-form-item label="开启自助注册" :name="['public', 'registerEnabled']">
<a-switch v-model:checked="formState.public.registerEnabled" /> <a-switch v-model:checked="formState.public.registerEnabled" />
</a-form-item> </a-form-item>
<a-form-item label="开启用户有效期" :name="['public', 'userValidTimeEnabled']">
<div class="flex-o">
<a-switch v-model:checked="formState.public.userValidTimeEnabled" :disabled="!settingsStore.isPlus" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">有效期内用户可正常使用失效后流水线将被停用</div>
</a-form-item>
<template v-if="formState.public.registerEnabled"> <template v-if="formState.public.registerEnabled">
<a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']"> <a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']">
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" /> <a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
@ -154,6 +161,14 @@ async function loadSysSettings() {
if (data?.private.sms?.type) { if (data?.private.sms?.type) {
await loadTypeDefine(data.private.sms.type); await loadTypeDefine(data.private.sms.type);
} }
if (!settingsStore.isPlus) {
formState.public.userValidTimeEnabled = false;
formState.public.emailRegisterEnabled = false;
}
if (!settingsStore.isComm) {
formState.public.smsLoginEnabled = false;
}
} }
const saveLoading = ref(false); const saveLoading = ref(false);

View File

@ -9,6 +9,6 @@ typeorm:
port: 3309 port: 3309
username: root username: root
password: root password: root
database: certd2 database: certd

View File

@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
* 修复用户最大流水线数量校验的问题 ([919f70a](https://github.com/certd/certd/commit/919f70a5fd2842ca69f96f1659bb5a7ba3f73776))
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
* 修复cv4pve sdk (proxmox插件连接失败时无法正常结束任务的bug) ([49f26b4](https://github.com/certd/certd/commit/49f26b4049a0549b0270395157e96e8f04a68bc4))
* 修复flexcdn部署证书的顶级CA名称显示 ([6467edb](https://github.com/certd/certd/commit/6467edb84324d7c80a85212675dbacedc459df83))
* 修复flexcdn证书commonNames错误的问题 ([ace363f](https://github.com/certd/certd/commit/ace363fa355436e769b27f71cc487d30d6441780))
### Performance Improvements
* 分组选择支持清空选项 ([03e2e99](https://github.com/certd/certd/commit/03e2e9949837b34eb3ea56d14a9e8a5dabc96063))
* 优化cname检查当有冲突的cname记录时给出提示 ([e639a8f](https://github.com/certd/certd/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
* 站点监控支持批量导入域名和ip ([2d7729d](https://github.com/certd/certd/commit/2d7729dbe98f29088f5f317db2b52cc1ede223a6))
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03) ## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
### Bug Fixes ### Bug Fixes

View File

@ -0,0 +1,3 @@
ALTER TABLE cd_cname_record ADD COLUMN `error` varchar(4096);
ALTER TABLE sys_user ADD COLUMN `valid_time` bigint;

View File

@ -0,0 +1,3 @@
ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
ALTER TABLE sys_user ADD COLUMN "valid_time" bigint;

View File

@ -0,0 +1,3 @@
ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
ALTER TABLE sys_user ADD COLUMN "valid_time" integer;

View File

@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-server", "name": "@certd/ui-server",
"version": "1.34.10", "version": "1.34.11",
"description": "fast-server base midway", "description": "fast-server base midway",
"private": true, "private": true,
"type": "module", "type": "module",
@ -42,20 +42,20 @@
"@aws-sdk/client-iam": "^3.699.0", "@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-cloudfront": "^3.699.0", "@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0", "@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.34.10", "@certd/acme-client": "^1.34.11",
"@certd/basic": "^1.34.10", "@certd/basic": "^1.34.11",
"@certd/commercial-core": "^1.34.10", "@certd/commercial-core": "^1.34.11",
"@certd/jdcloud": "^1.34.10", "@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/lib-huawei": "^1.34.10", "@certd/jdcloud": "^1.34.11",
"@certd/lib-k8s": "^1.34.10", "@certd/lib-huawei": "^1.34.11",
"@certd/lib-server": "^1.34.10", "@certd/lib-k8s": "^1.34.11",
"@certd/midway-flyway-js": "^1.34.10", "@certd/lib-server": "^1.34.11",
"@certd/pipeline": "^1.34.10", "@certd/midway-flyway-js": "^1.34.11",
"@certd/plugin-cert": "^1.34.10", "@certd/pipeline": "^1.34.11",
"@certd/plugin-lib": "^1.34.10", "@certd/plugin-cert": "^1.34.11",
"@certd/plugin-plus": "^1.34.10", "@certd/plugin-lib": "^1.34.11",
"@certd/plus-core": "^1.34.10", "@certd/plugin-plus": "^1.34.11",
"@corsinvest/cv4pve-api-javascript": "^8.3.0", "@certd/plus-core": "^1.34.11",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120", "@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120", "@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0", "@koa/cors": "^5.0.0",
@ -105,6 +105,7 @@
"otplib": "^12.0.1", "otplib": "^12.0.1",
"pg": "^8.12.0", "pg": "^8.12.0",
"psl": "^1.9.0", "psl": "^1.9.0",
"punycode.js": "^2.3.1",
"qiniu": "^7.12.0", "qiniu": "^7.12.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"qs": "^6.13.1", "qs": "^6.13.1",

View File

@ -105,6 +105,17 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
await this.service.checkAllByUsers(userId); await this.service.checkAllByUsers(userId);
return this.ok(); return this.ok();
} }
@Post('/import', { summary: Constants.per.authOnly })
async doImport(@Body(ALL) body: any) {
const userId = this.getUserId();
await this.service.doImport({
text:body.text,
userId
})
return this.ok();
}
@Post('/ipCheckChange', { summary: Constants.per.authOnly }) @Post('/ipCheckChange', { summary: Constants.per.authOnly })
async ipCheckChange(@Body(ALL) bean: any) { async ipCheckChange(@Body(ALL) bean: any) {
const userId = this.getUserId(); const userId = this.getUserId();

View File

@ -111,5 +111,16 @@ export class SiteInfoController extends CrudController<SiteIpService> {
return this.ok(); return this.ok();
} }
@Post('/import', { summary: Constants.per.authOnly })
async doImport(@Body(ALL) body: any) {
const userId = this.getUserId();
await this.service.doImport({
text:body.text,
userId,
siteId:body.siteId
})
return this.ok();
}
} }

View File

@ -26,6 +26,9 @@ export class CnameRecordEntity {
@Column({ comment: '验证状态', length: 20 }) @Column({ comment: '验证状态', length: 20 })
status: string; status: string;
@Column({ comment: '错误信息' })
error: string
@Column({ @Column({
comment: '创建时间', comment: '创建时间',
name: 'create_time', name: 'create_time',

View File

@ -6,13 +6,13 @@ import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.j
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert'; import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
import {CnameProvider, CnameRecord} from '@certd/pipeline'; import {CnameProvider, CnameRecord} from '@certd/pipeline';
import {cache, http, isDev, logger, utils} from '@certd/basic'; import {cache, http, isDev, logger, utils} from '@certd/basic';
import {walkTxtRecord} from '@certd/acme-client'; import {getAuthoritativeDnsResolver, walkTxtRecord} from '@certd/acme-client';
import {CnameProviderService} from './cname-provider-service.js'; import {CnameProviderService} from './cname-provider-service.js';
import {CnameProviderEntity} from '../entity/cname-provider.js'; import {CnameProviderEntity} from '../entity/cname-provider.js';
import {CommonDnsProvider} from './common-provider.js'; import {CommonDnsProvider} from './common-provider.js';
import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js"; import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js";
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js"; import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
import punycode from 'punycode.js'
type CnameCheckCacheValue = { type CnameCheckCacheValue = {
validating: boolean; validating: boolean;
pass: boolean; pass: boolean;
@ -22,6 +22,7 @@ type CnameCheckCacheValue = {
intervalId?: NodeJS.Timeout; intervalId?: NodeJS.Timeout;
dnsProvider?: IDnsProvider; dnsProvider?: IDnsProvider;
}; };
/** /**
* *
*/ */
@ -47,6 +48,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
getRepository() { getRepository() {
return this.repository; return this.repository;
} }
/** /**
* *
* @param param * @param param
@ -208,7 +210,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
if (isDev()) { if (isDev()) {
ttl = 30 * 1000; ttl = 30 * 1000;
} }
const testRecordValue = 'certd-cname-verify'; const testRecordValue = `certd-cname-verify-${bean.id}`;
const buildDnsProvider = async () => { const buildDnsProvider = async () => {
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId); const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
@ -271,6 +273,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`); logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
//检查是否有重复的acme配置
await this.checkRepeatAcmeChallengeRecords(fullDomain,bean.recordValue)
// const txtRecords = await dns.promises.resolveTxt(fullDomain); // const txtRecords = await dns.promises.resolveTxt(fullDomain);
// if (txtRecords.length) { // if (txtRecords.length) {
// records = [].concat(...txtRecords); // records = [].concat(...txtRecords);
@ -286,7 +291,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
if (success) { if (success) {
clearInterval(value.intervalId); clearInterval(value.intervalId);
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`); logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
await this.updateStatus(bean.id, 'valid'); await this.updateStatus(bean.id, 'valid', "");
value.pass = true; value.pass = true;
await clearVerifyRecord() await clearVerifyRecord()
return success; return success;
@ -312,24 +317,102 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
type: 'TXT', type: 'TXT',
value: testRecordValue, value: testRecordValue,
}; };
const dnsProvider = await buildDnsProvider(); const dnsProvider = await buildDnsProvider();
if(dnsProvider.usePunyCode()){
//是否需要中文转英文
req.domain = dnsProvider.punyCodeEncode(req.domain)
req.fullRecord = dnsProvider.punyCodeEncode(req.fullRecord)
req.hostRecord = dnsProvider.punyCodeEncode(req.hostRecord)
req.value = dnsProvider.punyCodeEncode(req.value)
}
const recordRes = await dnsProvider.createRecord(req); const recordRes = await dnsProvider.createRecord(req);
value.dnsProvider = dnsProvider; value.dnsProvider = dnsProvider;
value.validating = true; value.validating = true;
value.recordReq = req; value.recordReq = req;
value.recordRes = recordRes; value.recordRes = recordRes;
await this.updateStatus(bean.id, 'validating'); await this.updateStatus(bean.id, 'validating', "");
value.intervalId = setInterval(async () => { value.intervalId = setInterval(async () => {
try { try {
await checkRecordValue(); await checkRecordValue();
} catch (e) { } catch (e) {
logger.error('检查cname出错', e); logger.error('检查cname出错', e);
await this.updateError(bean.id, e.message);
} }
}, 10000); }, 10000);
} }
async updateStatus(id: number, status: CnameRecordStatusType) { async updateStatus(id: number, status: CnameRecordStatusType, error?: string) {
await this.getRepository().update(id, { status }); const updated: any = {status}
if (error != null) {
updated.error = error
}
await this.getRepository().update(id, updated);
}
async updateError(id: number, error: string) {
await this.getRepository().update(id, {error});
}
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string,targetCnameDomain:string) {
let dnsResolver = null
try{
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain)
}catch (e) {
logger.error(`获取${acmeRecordDomain}的权威DNS服务器失败${e.message}`)
return
}
let cnameRecords = []
try{
cnameRecords = await dnsResolver.resolveCname(acmeRecordDomain);
}catch (e) {
logger.error(`查询CNAME记录失败${e.message}`)
return
}
targetCnameDomain = targetCnameDomain.toLowerCase()
targetCnameDomain = punycode.toASCII(targetCnameDomain)
if (cnameRecords.length > 0) {
for (const cnameRecord of cnameRecords) {
if(cnameRecord.toLowerCase() !== targetCnameDomain){
//确保只有一个cname记录
throw new Error(`${acmeRecordDomain}存在多个CNAME记录请删除多余的CNAME记录${cnameRecord}`)
}
}
}
// 确保权威服务器里面没有纯粹的TXT记录
let txtRecords = []
try{
const txtRecordRes = await dnsResolver.resolveTxt(acmeRecordDomain);
if (txtRecordRes && txtRecordRes.length > 0) {
logger.info(`找到 ${txtRecordRes.length} 条 TXT记录 ${acmeRecordDomain}`);
logger.info(`TXT records: ${JSON.stringify(txtRecords)}`);
txtRecords = txtRecords.concat(...txtRecordRes);
}
}catch (e) {
logger.error(`查询Txt记录失败${e.message}`)
}
if (txtRecords.length === 0) {
//如果权威服务器中查不到txt无需继续检查
return
}
if (cnameRecords.length > 0) {
// 从cname记录中获取txt记录
// 对比是否存在如果不存在于cname中获取的txt中说明本体有创建多余的txt记录
const res = await walkTxtRecord(cnameRecords[0]);
if (res.length > 0) {
for (const txtRecord of txtRecords) {
if (!res.includes(txtRecord)) {
throw new Error(`${acmeRecordDomain}存在多个TXT记录请删除多余的TXT记录:${txtRecord}`)
}
}
}
}
} }
} }

View File

@ -1,6 +1,6 @@
import {CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert'; import {CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert';
import {PlusService} from '@certd/lib-server'; import {PlusService} from '@certd/lib-server';
import punycode from 'punycode.js'
export type CommonCnameProvider = { export type CommonCnameProvider = {
id: number; id: number;
domain: string; domain: string;
@ -24,6 +24,23 @@ export class CommonDnsProvider implements IDnsProvider {
this.plusService = opts.plusService; this.plusService = opts.plusService;
} }
/**
*
* @param domain
*/
punyCodeEncode(domain: string) {
return punycode.encode(domain);
}
/**
*
* @param domain
*/
punyCodeDecode(domain: string) {
return punycode.decode(domain);
}
usePunyCode(): boolean { usePunyCode(): boolean {
return false return false
} }

View File

@ -1,15 +1,15 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from '@certd/lib-server'; import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from 'typeorm'; import { Repository } from "typeorm";
import { SiteInfoEntity } from '../entity/site-info.js'; import { SiteInfoEntity } from "../entity/site-info.js";
import { siteTester } from './site-tester.js'; import { siteTester } from "./site-tester.js";
import dayjs from 'dayjs'; import dayjs from "dayjs";
import { logger, utils } from '@certd/basic'; import { logger, utils } from "@certd/basic";
import { PeerCertificate } from 'tls'; import { PeerCertificate } from "tls";
import { NotificationService } from '../../pipeline/service/notification-service.js'; import { NotificationService } from "../../pipeline/service/notification-service.js";
import { isComm, isPlus } from '@certd/plus-core'; import { isComm, isPlus } from "@certd/plus-core";
import { UserSuiteService } from '@certd/commercial-core'; import { UserSuiteService } from "@certd/commercial-core";
import { UserSettingsService } from "../../mine/service/user-settings-service.js"; import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { UserSiteMonitorSetting } from "../../mine/service/models.js"; import { UserSiteMonitorSetting } from "../../mine/service/models.js";
import { SiteIpService } from "./site-ip-service.js"; import { SiteIpService } from "./site-ip-service.js";
@ -43,7 +43,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async add(data: SiteInfoEntity) { async add(data: SiteInfoEntity) {
if (!data.userId) { if (!data.userId) {
throw new Error('userId is required'); throw new Error("userId is required");
} }
if (isComm()) { if (isComm()) {
@ -51,25 +51,34 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (suiteSetting.enabled) { if (suiteSetting.enabled) {
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId); const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) { if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
throw new NeedSuiteException('站点监控数量已达上限,请购买或升级套餐'); throw new NeedSuiteException("站点监控数量已达上限,请购买或升级套餐");
} }
} }
} else if (!isPlus()) { } else if (!isPlus()) {
const count = await this.getUserMonitorCount(data.userId); const count = await this.getUserMonitorCount(data.userId);
if (count >= 1) { if (count >= 1) {
throw new NeedVIPException('站点监控数量已达上限,请升级专业版'); throw new NeedVIPException("站点监控数量已达上限,请升级专业版");
} }
} }
data.disabled = false; data.disabled = false;
const found = await this.repository.findOne({
where: {
domain: data.domain,
userId: data.userId,
httpsPort: data.httpsPort || 443
}
});
if (found) {
return { id: found.id };
}
return await super.add(data); return await super.add(data);
} }
async update(data: any) { async update(data: any) {
if (!data.id) { if (!data.id) {
throw new Error('id is required'); throw new Error("id is required");
} }
delete data.userId; delete data.userId;
await super.update(data); await super.update(data);
@ -77,10 +86,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async getUserMonitorCount(userId: number) { async getUserMonitorCount(userId: number) {
if (!userId) { if (!userId) {
throw new Error('userId is required'); throw new Error("userId is required");
} }
return await this.repository.count({ return await this.repository.count({
where: { userId }, where: { userId }
}); });
} }
@ -92,26 +101,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
*/ */
async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) { async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) {
if (!site?.domain) { if (!site?.domain) {
throw new Error('站点域名不能为空'); throw new Error("站点域名不能为空");
} }
try { try {
await this.update({ await this.update({
id: site.id, id: site.id,
checkStatus: 'checking', checkStatus: "checking",
lastCheckTime: dayjs().valueOf(), lastCheckTime: dayjs().valueOf()
}); });
const res = await siteTester.test({ const res = await siteTester.test({
host: site.domain, host: site.domain,
port: site.httpsPort, port: site.httpsPort,
retryTimes, retryTimes
}); });
const certi: PeerCertificate = res.certificate; const certi: PeerCertificate = res.certificate;
if (!certi) { if (!certi) {
throw new Error('没有发现证书'); throw new Error("没有发现证书");
} }
const expires = certi.valid_to; const expires = certi.valid_to;
const allDomains = certi.subjectaltname?.replaceAll('DNS:', '').split(',') ||[]; const allDomains = certi.subjectaltname?.replaceAll("DNS:", "").split(",") || [];
const mainDomain = certi.subject?.CN; const mainDomain = certi.subject?.CN;
let domains = allDomains; let domains = allDomains;
if (!allDomains.includes(mainDomain)) { if (!allDomains.includes(mainDomain)) {
@ -119,23 +128,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
} }
const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`; const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`;
const isExpired = dayjs().valueOf() > dayjs(expires).valueOf(); const isExpired = dayjs().valueOf() > dayjs(expires).valueOf();
const status = isExpired ? 'expired' : 'ok'; const status = isExpired ? "expired" : "ok";
const updateData = { const updateData = {
id: site.id, id: site.id,
certDomains: domains.join(','), certDomains: domains.join(","),
certStatus: status, certStatus: status,
certProvider: issuer, certProvider: issuer,
certExpiresTime: dayjs(expires).valueOf(), certExpiresTime: dayjs(expires).valueOf(),
lastCheckTime: dayjs().valueOf(), lastCheckTime: dayjs().valueOf(),
error: null, error: null,
checkStatus: 'ok', checkStatus: "ok"
}; };
if (site.ipCheck) {
delete updateData.checkStatus
}
await this.update(updateData); await this.update(updateData);
//检查ip //检查ip
await this.checkAllIp(site) await this.checkAllIp(site);
if (!notify) { if (!notify) {
return; return;
@ -143,15 +155,15 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
try { try {
await this.sendExpiresNotify(site); await this.sendExpiresNotify(site);
} catch (e) { } catch (e) {
logger.error('send notify error', e); logger.error("send notify error", e);
} }
} catch (e) { } catch (e) {
logger.error('check site error', e); logger.error("check site error", e);
await this.update({ await this.update({
id: site.id, id: site.id,
checkStatus: 'error', checkStatus: "error",
lastCheckTime: dayjs().valueOf(), lastCheckTime: dayjs().valueOf(),
error: e.message, error: e.message
}); });
if (!notify) { if (!notify) {
return; return;
@ -159,7 +171,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
try { try {
await this.sendCheckErrorNotify(site); await this.sendCheckErrorNotify(site);
} catch (e) { } catch (e) {
logger.error('send notify error', e); logger.error("send notify error", e);
} }
} }
} }
@ -170,38 +182,45 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
} }
const certExpiresTime = site.certExpiresTime; const certExpiresTime = site.certExpiresTime;
const onFinished = async (list: SiteIpEntity[]) => { const onFinished = async (list: SiteIpEntity[]) => {
let errorCount = 0 let errorCount = 0;
let errorMessage = "" let errorMessage = "";
for (const item of list) { for (const item of list) {
if (!item) { if (!item) {
continue; continue;
} }
errorCount++ errorCount++;
if (item.error) { if (item.error) {
errorMessage += `${item.ipAddress}${item.error} \n` errorMessage += `${item.ipAddress}${item.error} \n`;
} else if (item.certExpiresTime !== certExpiresTime) { } else if (item.certExpiresTime !== certExpiresTime) {
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n` errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`;
} else { } else {
errorCount-- errorCount--;
} }
} }
if (errorCount <= 0) { if (errorCount <= 0) {
return //检查正常
await this.update({
id: site.id,
checkStatus: "ok",
error: "",
ipErrorCount: 0
});
return;
} }
await this.update({ await this.update({
id: site.id, id: site.id,
checkStatus: 'error', checkStatus: "error",
error: errorMessage, error: errorMessage,
ipErrorCount: errorCount, ipErrorCount: errorCount
}) });
try { try {
site = await this.info(site.id) site = await this.info(site.id);
await this.sendCheckErrorNotify(site, true); await this.sendCheckErrorNotify(site, true);
} catch (e) { } catch (e) {
logger.error('send notify error', e); logger.error("send notify error", e);
} }
} };
await this.siteIpService.checkAll(site,onFinished) await this.siteIpService.checkAll(site, onFinished);
} }
/** /**
@ -213,13 +232,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async check(id: number, notify = false, retryTimes = 3) { async check(id: number, notify = false, retryTimes = 3) {
const site = await this.info(id); const site = await this.info(id);
if (!site) { if (!site) {
throw new Error('站点不存在'); throw new Error("站点不存在");
} }
return await this.doCheck(site, notify, retryTimes); return await this.doCheck(site, notify, retryTimes);
} }
async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) { async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) {
const url = await this.notificationService.getBindUrl('#/certd/monitor/site'); const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
// 发邮件 // 发邮件
await this.notificationService.send( await this.notificationService.send(
{ {
@ -229,20 +248,21 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
url, url,
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`, title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
content: `站点名称: ${site.name} \n站点域名 ${site.domain} \n错误信息${site.error}`, content: `站点名称: ${site.name} \n站点域名 ${site.domain} \n错误信息${site.error}`,
errorMessage: site.error, errorMessage: site.error
}, }
}, },
site.userId site.userId
); );
} }
async sendExpiresNotify(site: SiteInfoEntity) { async sendExpiresNotify(site: SiteInfoEntity) {
const tipDays = 10 const tipDays = 10;
const expires = site.certExpiresTime; const expires = site.certExpiresTime;
const validDays = dayjs(expires).diff(dayjs(), 'day'); const validDays = dayjs(expires).diff(dayjs(), "day");
const url = await this.notificationService.getBindUrl('#/certd/monitor/site'); const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
const content = `站点名称: ${site.name} \n站点域名 ${site.domain} \n证书域名 ${site.certDomains} \n颁发机构 ${site.certProvider} \n过期时间 ${dayjs(site.certExpiresTime).format('YYYY-MM-DD')} \n`; const content = `站点名称: ${site.name} \n站点域名 ${site.domain} \n证书域名 ${site.certDomains} \n颁发机构 ${site.certProvider} \n过期时间 ${dayjs(site.certExpiresTime).format("YYYY-MM-DD")} \n`;
if (validDays >= 0 && validDays < tipDays) { if (validDays >= 0 && validDays < tipDays) {
// 发通知 // 发通知
await this.notificationService.send( await this.notificationService.send(
@ -252,8 +272,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
body: { body: {
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`, title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
content, content,
url, url
}, }
}, },
site.userId site.userId
); );
@ -268,7 +288,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
content, content,
url, url,
errorMessage: "站点证书已过期" errorMessage: "站点证书已过期"
}, }
}, },
site.userId site.userId
); );
@ -277,10 +297,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async checkAllByUsers(userId: any) { async checkAllByUsers(userId: any) {
if (!userId) { if (!userId) {
throw new Error('userId is required'); throw new Error("userId is required");
} }
const sites = await this.repository.find({ const sites = await this.repository.find({
where: { userId }, where: { userId }
}); });
this.checkList(sites); this.checkList(sites);
} }
@ -306,22 +326,74 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
await this.update({ await this.update({
id: req.id, id: req.id,
ipCheck: req.ipCheck, ipCheck: req.ipCheck
}); });
if (req.ipCheck) { if (req.ipCheck) {
const site = await this.info(req.id); const site = await this.info(req.id);
await this.siteIpService.sync(site) await this.siteIpService.sync(site);
} }
} }
async disabledChange(req: { disabled: any; id: any }) { async disabledChange(req: { disabled: any; id: any }) {
await this.update({ await this.update({
id: req.id, id: req.id,
disabled: req.disabled, disabled: req.disabled
}); });
if (!req.disabled) { if (!req.disabled) {
const site = await this.info(req.id); const site = await this.info(req.id);
await this.doCheck(site) await this.doCheck(site);
} }
} }
async doImport(req: { text: string; userId: number }) {
if (!req.text) {
throw new Error("text is required");
}
if (!req.userId) {
throw new Error("userId is required");
}
const rows = req.text.split("\n");
const list = [];
for (const item of rows) {
if (!item) {
continue;
}
const arr = item.trim().split(":");
if (arr.length === 0) {
continue;
}
const domain = arr[0];
let port = 443;
let name = domain;
if (arr.length > 1) {
try {
port = parseInt(arr[1] || "443");
} catch (e) {
throw new Error(`${item}格式错误`);
}
}
if (arr.length > 2) {
name = arr[2] || domain;
}
list.push({
domain,
name,
httpsPort: port,
userId: req.userId
});
}
const batchAdd = async (list: any[]) => {
for (const item of list) {
await this.add(item);
}
// await this.checkAllByUsers(req.userId);
};
await batchAdd(list);
}
} }

View File

@ -182,7 +182,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
const finished = res.filter(item=>{ const finished = res.filter(item=>{
return item!=null return item!=null
}) })
if (finished.length > 0) { if (onFinish) {
onFinish && onFinish(finished) onFinish && onFinish(finished)
} }
}) })
@ -232,4 +232,50 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
ipCount:count ipCount:count
}) })
} }
async doImport(req: { text: string; userId:number, siteId:number }) {
if (!req.text) {
throw new Error("text is required");
}
if (!req.siteId) {
throw new Error("siteId is required");
}
const siteEntity = await this.siteInfoRepository.findOne({
where: {
id: req.siteId,
userId:req.userId
}
});
if (!siteEntity) {
throw new Error(`站点${req.siteId}不存在`);
}
const userId = siteEntity.userId;
const rows = req.text.split("\n");
const list = [];
for (const item of rows) {
if (!item) {
continue;
}
list.push({
ipAddress:item,
userId: userId,
siteId: req.siteId,
from: "import",
disabled:false,
});
}
const batchAdd = async (list: any[]) => {
for (const item of list) {
await this.add(item);
}
// await this.checkAllByUsers(req.userId);
};
await batchAdd(list);
}
} }

View File

@ -233,8 +233,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`); throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
} }
} }
} }else{
//非商业版校验用户最大流水线数量
const userId = bean.userId; const userId = bean.userId;
const userIsAdmin = await this.userService.isAdmin(userId); const userIsAdmin = await this.userService.isAdmin(userId);
if (!userIsAdmin) { if (!userIsAdmin) {
@ -247,6 +247,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
} }
} }
} }
}
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) { async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
const idEntityList = await this.repository.find({ const idEntityList = await this.repository.find({
@ -329,6 +330,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) { if (isComm()) {
await this.checkHasDeployCount(id, entity.userId); await this.checkHasDeployCount(id, entity.userId);
} }
await this.checkUserStatus(entity.userId)
this.cron.register({ this.cron.register({
name: `pipeline.${id}.trigger.once`, name: `pipeline.${id}.trigger.once`,
cron: null, cron: null,
@ -446,6 +448,13 @@ export class PipelineService extends BaseService<PipelineEntity> {
if (isComm()) { if (isComm()) {
suite = await this.checkHasDeployCount(id, entity.userId); suite = await this.checkHasDeployCount(id, entity.userId);
} }
try{
await this.checkUserStatus(entity.userId)
}catch (e) {
logger.info(e.message)
return
}
const pipeline = JSON.parse(entity.content); const pipeline = JSON.parse(entity.content);
if (!pipeline.id) { if (!pipeline.id) {
@ -745,5 +754,25 @@ export class PipelineService extends BaseService<PipelineEntity> {
} }
private async checkUserStatus(userId: number) {
const userEntity = await this.userService.info(userId);
if(userEntity == null){
throw new Error('用户不存在');
}
if(userEntity.status === 0){
const message = `账户${userId}已被禁用,禁止运行流水线`
throw new Error(message)
}
const sysPublic = await this.sysSettingsService.getPublicSettings()
if(isPlus() && sysPublic.userValidTimeEnabled === true){
//校验用户有效期是否设置
if(userEntity.validTime!= null && userEntity.validTime > 0){
if(userEntity.validTime < new Date().getTime()){
//用户已过期
const message = `账户${userId}已过有效期,禁止运行流水线`
throw new Error(message)
}
}
}
}
} }

View File

@ -37,6 +37,10 @@ export class UserEntity {
@Column({ comment: '状态 0:禁用 1启用', default: 1 }) @Column({ comment: '状态 0:禁用 1启用', default: 1 })
status: number; status: number;
@Column({ name: 'valid_time', comment: '有效期', nullable: true })
validTime: number;
@Column({ @Column({
name: 'create_time', name: 'create_time',
comment: '创建时间', comment: '创建时间',

View File

@ -7,7 +7,7 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
title: '阿里云-部署证书至OSS', title: '阿里云-部署证书至OSS',
icon: 'svg:icon-aliyun', icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key, group: pluginGroups.aliyun.key,
desc: '自动部署域名证书至阿里云OSS', desc: '部署域名证书至阿里云OSS自定义域名不是上传到阿里云oss',
default: { default: {
strategy: { strategy: {
runStrategy: RunStrategy.SkipWhenSucceed, runStrategy: RunStrategy.SkipWhenSucceed,

View File

@ -26,6 +26,11 @@ export type CloudflareRecord = {
}) })
export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord> { export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord> {
access!: CloudflareAccess; access!: CloudflareAccess;
usePunyCode(): boolean {
//是否使用punycode来添加解析记录
//默认都使用原始中文域名来添加
return true;
}
async onInstance() { async onInstance() {
//一些初始化的操作 //一些初始化的操作
// 也可以通过ctx成员变量传递context // 也可以通过ctx成员变量传递context

View File

@ -91,8 +91,9 @@ export class FlexCDNRefreshCert extends AbstractTaskPlugin {
* timeEndAt: Math.floor((new Date(currentInfo.validTo)).getTime() / 1000), * timeEndAt: Math.floor((new Date(currentInfo.validTo)).getTime() / 1000),
* *
*/ */
const commonNames =[ certReader.getMainDomain()] const topCrt = CertReader.readCertDetail(certReader.cert.ic)
const dnsNames = certReader.getAltNames() const commonNames =[ topCrt.detail.issuer.commonName]
const dnsNames = certReader.getAllDomains()
const timeBeginAt = Math.floor(certReader.detail.notBefore.getTime() / 1000); const timeBeginAt = Math.floor(certReader.detail.notBefore.getTime() / 1000);
const timeEndAt = Math.floor(certReader.detail.notAfter.getTime() / 1000); const timeEndAt = Math.floor(certReader.detail.notAfter.getTime() / 1000);
const body = { const body = {

View File

@ -77,7 +77,13 @@ export class GithubCheckRelease extends AbstractTaskPlugin {
this.logger.info(`有更新,${lastVersion??"0"}->${res.tag_name}`) this.logger.info(`有更新,${lastVersion??"0"}->${res.tag_name}`)
this.lastVersion = res.tag_name; this.lastVersion = res.tag_name;
const body = res.body.replaceAll("* ","- ") // const body = res.body.replaceAll("* ","- ")
//仅每行开头的* 替换成 - *号前面可以有空格
const body = res.body.replace(/^(\s*)\* /gm, "$1- ")
if (this.notificationIds == null){
this.notificationIds = [0]
}
//发送通知 //发送通知
for (const notificationId of this.notificationIds) { for (const notificationId of this.notificationIds) {
await this.ctx.notificationService.send({ await this.ctx.notificationService.send({

View File

@ -23,6 +23,11 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
// 也可以通过ctx成员变量传递context // 也可以通过ctx成员变量传递context
this.access = this.ctx.access as NamesiloAccess; this.access = this.ctx.access as NamesiloAccess;
} }
usePunyCode(): boolean {
//是否使用punycode来添加解析记录
//默认都使用原始中文域名来添加
return true;
}
private async doRequest(url: string, params: any = null) { private async doRequest(url: string, params: any = null) {
params = merge( params = merge(

View File

@ -93,7 +93,7 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
async getClient() { async getClient() {
const access: ProxmoxAccess = await this.getAccess<ProxmoxAccess>(this.accessId); const access: ProxmoxAccess = await this.getAccess<ProxmoxAccess>(this.accessId);
const pve = await import('@corsinvest/cv4pve-api-javascript'); const pve = await import('@certd/cv4pve-api-javascript');
const client = new pve.PveClient(access.host, access.port); const client = new pve.PveClient(access.host, access.port);
const login = await client.login(access.username, access.password, access.realm || 'pam'); const login = await client.login(access.username, access.password, access.realm || 'pam');
if (!login) { if (!login) {

View File

@ -46,7 +46,7 @@ importers:
packages/core/acme-client: packages/core/acme-client:
dependencies: dependencies:
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../basic version: link:../basic
'@peculiar/x509': '@peculiar/x509':
specifier: ^1.11.0 specifier: ^1.11.0
@ -72,7 +72,7 @@ importers:
node-forge: node-forge:
specifier: ^1.3.1 specifier: ^1.3.1
version: 1.3.1 version: 1.3.1
punycode: punycode.js:
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.1 version: 2.3.1
devDependencies: devDependencies:
@ -207,10 +207,10 @@ importers:
packages/core/pipeline: packages/core/pipeline:
dependencies: dependencies:
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../basic version: link:../basic
'@certd/plus-core': '@certd/plus-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../pro/plus-core version: link:../../pro/plus-core
dayjs: dayjs:
specifier: ^1.11.7 specifier: ^1.11.7
@ -415,7 +415,7 @@ importers:
packages/libs/lib-k8s: packages/libs/lib-k8s:
dependencies: dependencies:
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@kubernetes/client-node': '@kubernetes/client-node':
specifier: 0.21.0 specifier: 0.21.0
@ -455,16 +455,16 @@ importers:
packages/libs/lib-server: packages/libs/lib-server:
dependencies: dependencies:
'@certd/acme-client': '@certd/acme-client':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/acme-client version: link:../../core/acme-client
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@certd/plus-core': '@certd/plus-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../pro/plus-core version: link:../../pro/plus-core
'@midwayjs/cache': '@midwayjs/cache':
specifier: ~3.14.0 specifier: ~3.14.0
@ -607,16 +607,16 @@ importers:
packages/plugins/plugin-cert: packages/plugins/plugin-cert:
dependencies: dependencies:
'@certd/acme-client': '@certd/acme-client':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/acme-client version: link:../../core/acme-client
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@certd/plugin-lib': '@certd/plugin-lib':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../plugin-lib version: link:../plugin-lib
'@google-cloud/publicca': '@google-cloud/publicca':
specifier: ^1.3.0 specifier: ^1.3.0
@ -633,7 +633,7 @@ importers:
psl: psl:
specifier: ^1.9.0 specifier: ^1.9.0
version: 1.15.0 version: 1.15.0
punycode: punycode.js:
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.1 version: 2.3.1
rimraf: rimraf:
@ -682,9 +682,6 @@ importers:
packages/plugins/plugin-lib: packages/plugins/plugin-lib:
dependencies: dependencies:
'@alicloud/credentials':
specifier: ^2.4.3
version: 2.4.3
'@alicloud/openapi-client': '@alicloud/openapi-client':
specifier: ^0.4.14 specifier: ^0.4.14
version: 0.4.14 version: 0.4.14
@ -698,10 +695,10 @@ importers:
specifier: ^3.787.0 specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2) version: 3.810.0(aws-crt@1.26.2)
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@kubernetes/client-node': '@kubernetes/client-node':
specifier: 0.21.0 specifier: 0.21.0
@ -789,19 +786,19 @@ importers:
packages/pro/commercial-core: packages/pro/commercial-core:
dependencies: dependencies:
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/lib-server': '@certd/lib-server':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-server version: link:../../libs/lib-server
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@certd/plugin-plus': '@certd/plugin-plus':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../plugin-plus version: link:../plugin-plus
'@certd/plus-core': '@certd/plus-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../plus-core version: link:../plus-core
'@midwayjs/core': '@midwayjs/core':
specifier: ~3.20.3 specifier: ~3.20.3
@ -886,22 +883,22 @@ importers:
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.3 version: 1.0.3
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/lib-k8s': '@certd/lib-k8s':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-k8s version: link:../../libs/lib-k8s
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@certd/plugin-cert': '@certd/plugin-cert':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../plugins/plugin-cert version: link:../../plugins/plugin-cert
'@certd/plugin-lib': '@certd/plugin-lib':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../plugins/plugin-lib version: link:../../plugins/plugin-lib
'@certd/plus-core': '@certd/plus-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../plus-core version: link:../plus-core
ali-oss: ali-oss:
specifier: ^6.21.0 specifier: ^6.21.0
@ -1004,7 +1001,7 @@ importers:
packages/pro/plus-core: packages/pro/plus-core:
dependencies: dependencies:
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
dayjs: dayjs:
specifier: ^1.11.7 specifier: ^1.11.7
@ -1294,10 +1291,10 @@ importers:
version: 0.1.3(zod@3.24.4) version: 0.1.3(zod@3.24.4)
devDependencies: devDependencies:
'@certd/lib-iframe': '@certd/lib-iframe':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-iframe version: link:../../libs/lib-iframe
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@rollup/plugin-commonjs': '@rollup/plugin-commonjs':
specifier: ^25.0.7 specifier: ^25.0.7
@ -1477,47 +1474,47 @@ importers:
specifier: ^3.705.0 specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2) version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client': '@certd/acme-client':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/acme-client version: link:../../core/acme-client
'@certd/basic': '@certd/basic':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/basic version: link:../../core/basic
'@certd/commercial-core': '@certd/commercial-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../pro/commercial-core version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.1
version: 8.4.1
'@certd/jdcloud': '@certd/jdcloud':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-jdcloud version: link:../../libs/lib-jdcloud
'@certd/lib-huawei': '@certd/lib-huawei':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-huawei version: link:../../libs/lib-huawei
'@certd/lib-k8s': '@certd/lib-k8s':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-k8s version: link:../../libs/lib-k8s
'@certd/lib-server': '@certd/lib-server':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/lib-server version: link:../../libs/lib-server
'@certd/midway-flyway-js': '@certd/midway-flyway-js':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../libs/midway-flyway-js version: link:../../libs/midway-flyway-js
'@certd/pipeline': '@certd/pipeline':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../core/pipeline version: link:../../core/pipeline
'@certd/plugin-cert': '@certd/plugin-cert':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../plugins/plugin-cert version: link:../../plugins/plugin-cert
'@certd/plugin-lib': '@certd/plugin-lib':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../plugins/plugin-lib version: link:../../plugins/plugin-lib
'@certd/plugin-plus': '@certd/plugin-plus':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../pro/plugin-plus version: link:../../pro/plugin-plus
'@certd/plus-core': '@certd/plus-core':
specifier: ^1.34.7 specifier: ^1.34.10
version: link:../../pro/plus-core version: link:../../pro/plus-core
'@corsinvest/cv4pve-api-javascript':
specifier: ^8.3.0
version: 8.4.0
'@huaweicloud/huaweicloud-sdk-cdn': '@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120 specifier: ^3.1.120
version: 3.1.149 version: 3.1.149
@ -1665,6 +1662,9 @@ importers:
psl: psl:
specifier: ^1.9.0 specifier: ^1.9.0
version: 1.15.0 version: 1.15.0
punycode.js:
specifier: ^2.3.1
version: 2.3.1
qiniu: qiniu:
specifier: ^7.12.0 specifier: ^7.12.0
version: 7.14.0 version: 7.14.0
@ -2659,13 +2659,13 @@ packages:
'@better-scroll/zoom@2.5.1': '@better-scroll/zoom@2.5.1':
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==} resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
'@certd/cv4pve-api-javascript@8.4.1':
resolution: {integrity: sha512-jxlRieJmCA0Z9LnwX6Ra6ZekRGJEu8o8RGYoKU0Jjkhc9jm6ChEbVyfE7Iw49/hlpA+2yaHdAXb46au/afCISg==}
'@colors/colors@1.5.0': '@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'} engines: {node: '>=0.1.90'}
'@corsinvest/cv4pve-api-javascript@8.4.0':
resolution: {integrity: sha512-SDL+Y+t+/NkXmfOnRgil2ujMv/gfnah88n4+rMFBLv3qYjSy5uNetoeMmtLoR1wSlJmOvktzxQjsBl0TZt0gCw==}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -14881,15 +14881,15 @@ snapshots:
dependencies: dependencies:
'@better-scroll/core': 2.5.1 '@better-scroll/core': 2.5.1
'@colors/colors@1.5.0': '@certd/cv4pve-api-javascript@8.4.1':
optional: true
'@corsinvest/cv4pve-api-javascript@8.4.0':
dependencies: dependencies:
debug: 4.4.1(supports-color@8.1.1) debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@colors/colors@1.5.0':
optional: true
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
@ -20740,13 +20740,13 @@ snapshots:
resolve: 1.22.10 resolve: 1.22.10
semver: 6.3.1 semver: 6.3.1
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8): eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
dependencies: dependencies:
eslint: 7.32.0 eslint: 7.32.0
prettier: 2.8.8 prettier: 2.8.8
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
optionalDependencies: optionalDependencies:
eslint-config-prettier: 8.10.0(eslint@8.57.0) eslint-config-prettier: 8.10.0(eslint@7.32.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): 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: dependencies:
@ -23454,7 +23454,7 @@ snapshots:
eslint: 7.32.0 eslint: 7.32.0
eslint-config-prettier: 8.10.0(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-node: 11.1.0(eslint@7.32.0)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8) eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
execa: 5.1.1 execa: 5.1.1
inquirer: 7.3.3 inquirer: 7.3.3
json5: 2.2.3 json5: 2.2.3