{
return false;
}
+ /**
+ * 中文转英文
+ * @param domain
+ */
+ punyCodeEncode(domain: string) {
+ return punycode.toASCII(domain);
+ }
+
+ /**
+ * 转中文域名
+ * @param domain
+ */
+ punyCodeDecode(domain: string) {
+ return punycode.toUnicode(domain);
+ }
+
setCtx(ctx: DnsProviderContext) {
this.ctx = ctx;
this.logger = ctx.logger;
diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts
index beb99697..d9774786 100644
--- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts
+++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts
@@ -6,7 +6,7 @@ import { Challenge } from "@certd/acme-client/types/rfc8555";
import { IContext } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic";
import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js";
-import punycode from "node:punycode";
+import punycode from "punycode.js";
import { IOssClient } from "@certd/plugin-lib";
export type CnameVerifyPlan = {
type?: string;
@@ -233,16 +233,18 @@ export class AcmeService {
let dnsProvider = providers.dnsProvider;
let fullRecord = `_acme-challenge.${fullDomain}`;
+ const origDomain = punycode.toUnicode(domain);
+ const origFullDomain = punycode.toUnicode(fullDomain);
if (providers.domainsVerifyPlan) {
//按照计划执行
- const domainVerifyPlan = providers.domainsVerifyPlan[domain];
+ const domainVerifyPlan = providers.domainsVerifyPlan[origDomain];
if (domainVerifyPlan) {
if (domainVerifyPlan.type === "dns") {
dnsProvider = domainVerifyPlan.dnsProvider;
} else if (domainVerifyPlan.type === "cname") {
const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
if (cnameVerifyPlan) {
- const cname = cnameVerifyPlan[fullDomain];
+ const cname = cnameVerifyPlan[origFullDomain];
if (cname) {
dnsProvider = cname.dnsProvider;
domain = await this.options.domainParser.parse(cname.domain);
diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts
index e2478bfa..bdffbb9d 100644
--- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts
+++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts
@@ -5,6 +5,7 @@ import path from "path";
import { CertificateInfo, crypto } from "@certd/acme-client";
import { ILogger } from "@certd/basic";
import dayjs from "dayjs";
+import { uniq } from "lodash-es";
export type CertReaderHandleContext = {
reader: CertReader;
@@ -90,7 +91,8 @@ export class CertReader {
const { detail } = this.getCrtDetail();
const domains = [detail.domains.commonName];
domains.push(...detail.domains.altNames);
- return domains;
+ //去重
+ return uniq(domains);
}
getAltNames() {
diff --git a/packages/plugins/plugin-lib/CHANGELOG.md b/packages/plugins/plugin-lib/CHANGELOG.md
index 81032944..900764bd 100644
--- a/packages/plugins/plugin-lib/CHANGELOG.md
+++ b/packages/plugins/plugin-lib/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.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)
### Performance Improvements
diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json
index 26989012..88264ea3 100644
--- a/packages/plugins/plugin-lib/package.json
+++ b/packages/plugins/plugin-lib/package.json
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
- "version": "1.34.10",
+ "version": "1.34.11",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -20,8 +20,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
- "@certd/basic": "^1.34.10",
- "@certd/pipeline": "^1.34.10",
+ "@certd/basic": "^1.34.11",
+ "@certd/pipeline": "^1.34.11",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -52,5 +52,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
- "gitHead": "a4b6580247efabe948507c771a177d4f75670bc2"
+ "gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
}
diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md
index a15ab349..59127923 100644
--- a/packages/ui/certd-client/CHANGELOG.md
+++ b/packages/ui/certd-client/CHANGELOG.md
@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
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)
### Performance Improvements
diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json
index aa12551b..9db439e4 100644
--- a/packages/ui/certd-client/package.json
+++ b/packages/ui/certd-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
- "version": "1.34.10",
+ "version": "1.34.11",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -9,7 +9,7 @@
"debug": "vite --mode debug --open",
"debug:pm": "vite --mode debugpm",
"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",
"test:unit": "vitest",
"serve": "vite preview",
@@ -102,8 +102,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
- "@certd/lib-iframe": "^1.34.10",
- "@certd/pipeline": "^1.34.10",
+ "@certd/lib-iframe": "^1.34.11",
+ "@certd/pipeline": "^1.34.11",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
diff --git a/packages/ui/certd-client/public/template-import.csv b/packages/ui/certd-client/public/site-import-template.csv
similarity index 100%
rename from packages/ui/certd-client/public/template-import.csv
rename to packages/ui/certd-client/public/site-import-template.csv
diff --git a/packages/ui/certd-client/src/components/index.ts b/packages/ui/certd-client/src/components/index.ts
index 008752a2..7f08df50 100644
--- a/packages/ui/certd-client/src/components/index.ts
+++ b/packages/ui/certd-client/src/components/index.ts
@@ -15,6 +15,7 @@ import PemInput from "./pem-input.vue";
import { defineAsyncComponent } from "vue";
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
import EmailSelector from "./email-selector/index.vue";
+import ValidTimeFormat from "./valid-time-format.vue";
export default {
install(app: any) {
app.component(
@@ -27,6 +28,7 @@ export default {
app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput);
app.component("PemInput", PemInput);
+ app.component("ValidTimeFormat", ValidTimeFormat);
// app.component("CodeEditor", CodeEditor);
app.component("CronLight", CronLight);
diff --git a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/api.ts b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/api.ts
index 9efd7833..01ffa5ec 100644
--- a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/api.ts
+++ b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/api.ts
@@ -8,6 +8,7 @@ export type CnameRecord = {
status?: string;
hostRecord?: string;
recordValue?: string;
+ error?: string;
};
export type DomainGroupItem = {
diff --git a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue
index 3f814dff..ebee7a67 100644
--- a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue
+++ b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue
@@ -13,6 +13,9 @@
+
+
+
|
diff --git a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-verify-plan.vue b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-verify-plan.vue
index 3e12db97..4e49fea7 100644
--- a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-verify-plan.vue
+++ b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/cname-verify-plan.vue
@@ -21,7 +21,7 @@ import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-edit
import { computed } from "vue";
defineOptions({
- name: "CnameVerifyPlan"
+ name: "CnameVerifyPlan",
});
const emit = defineEmits(["update:modelValue", "change"]);
diff --git a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/index.vue b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/index.vue
index 14136eb3..8a533211 100644
--- a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/index.vue
+++ b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/index.vue
@@ -55,6 +55,7 @@
+ 证书颁发机构将请求 https://yourdomain/.well-known/acme-challenge/xxxxxx 来验证域名所有权。
|
diff --git a/packages/ui/certd-client/src/components/valid-time-format.vue b/packages/ui/certd-client/src/components/valid-time-format.vue
new file mode 100644
index 00000000..2abfff72
--- /dev/null
+++ b/packages/ui/certd-client/src/components/valid-time-format.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/packages/ui/certd-client/src/components/vip-button/index.vue b/packages/ui/certd-client/src/components/vip-button/index.vue
index 4b90018c..91a93064 100644
--- a/packages/ui/certd-client/src/components/vip-button/index.vue
+++ b/packages/ui/certd-client/src/components/vip-button/index.vue
@@ -233,13 +233,13 @@ function openUpgrade() {
desc: "社区免费版",
type: "free",
icon: "lucide:package-open",
- privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件", "邮件、webhook通知方式"],
+ privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn、宝塔、1Panel等部署插件", "邮件、webhook通知方式"],
},
plus: {
title: "专业版",
desc: "开源需要您的赞助支持",
type: "plus",
- privilege: ["可加VIP群,您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,更多强大的部署插件,宝塔、群晖、1Panel等"],
+ privilege: ["可加VIP群,您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,群辉等更多插件"],
trial: {
title: "点击获取7天试用",
click: () => {
diff --git a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx
index ba607ac5..5b098e7b 100644
--- a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx
+++ b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx
@@ -120,10 +120,10 @@ function install(app: App, options: any = {}) {
rowHandle: {
fixed: "right",
buttons: {
- view: { type: "link", text: null, icon: "ion:eye-outline" },
- copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" },
- edit: { type: "link", text: null, icon: "ion:create-outline" },
- remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" },
+ view: { type: "link", text: null, icon: "ion:eye-outline", tooltip: { title: "查看" } },
+ copy: { show: true, type: "link", text: null, icon: "ion:copy-outline", tooltip: { title: "复制" } },
+ edit: { type: "link", text: null, icon: "ion:create-outline", tooltip: { title: "编辑" } },
+ remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline", tooltip: { title: "删除" } },
},
dropdown: {
more: {
diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts
index 15d42512..77b81e0a 100644
--- a/packages/ui/certd-client/src/router/source/modules/certd.ts
+++ b/packages/ui/certd-client/src/router/source/modules/certd.ts
@@ -122,7 +122,7 @@ export const certdResources = [
},
{
- title: "OpenKey",
+ title: "开放接口密钥",
name: "OpenKey",
path: "/certd/open/openkey",
component: "/certd/open/openkey/index.vue",
diff --git a/packages/ui/certd-client/src/store/settings/api.basic.ts b/packages/ui/certd-client/src/store/settings/api.basic.ts
index b53a6409..d7a32866 100644
--- a/packages/ui/certd-client/src/store/settings/api.basic.ts
+++ b/packages/ui/certd-client/src/store/settings/api.basic.ts
@@ -30,6 +30,7 @@ export type PlusInfo = {
};
export type SysPublicSetting = {
registerEnabled?: boolean;
+ userValidTimeEnabled?: boolean;
usernameRegisterEnabled?: boolean;
mobileRegisterEnabled?: boolean;
emailRegisterEnabled?: boolean;
diff --git a/packages/ui/certd-client/src/store/user/api.user.ts b/packages/ui/certd-client/src/store/user/api.user.ts
index 9e81b51b..bfd61d41 100644
--- a/packages/ui/certd-client/src/store/user/api.user.ts
+++ b/packages/ui/certd-client/src/store/user/api.user.ts
@@ -27,6 +27,8 @@ export interface UserInfoRes {
avatar?: string;
roleIds: number[];
isWeak?: boolean;
+ validTime?: number;
+ status?: number;
}
export interface LoginRes {
diff --git a/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx b/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx
index ddc89e77..c25d0faa 100644
--- a/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx
+++ b/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx
@@ -2,7 +2,7 @@ import * as api from "./api";
import { useI18n } from "vue-i18n";
import { Ref, ref } from "vue";
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 { useSettingStore } from "/@/store/settings";
import { message } from "ant-design-vue";
@@ -31,7 +31,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const settingStore = useSettingStore();
const selectedRowKeys: Ref = ref([]);
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 {
crudOptions: {
settings: {
@@ -174,21 +182,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
status: {
title: "状态",
type: "dict-select",
- dict: 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" },
- ],
- }),
+ dict: dictRef,
addForm: {
show: false,
},
column: {
width: 120,
align: "center",
+ cellRender({ value, row }) {
+ return (
+
+
+ {row.error && (
+
+
+
+ )}
+
+ );
+ },
},
},
triggerValidate: {
diff --git a/packages/ui/certd-client/src/views/certd/history/api.ts b/packages/ui/certd-client/src/views/certd/history/api.ts
index eae98960..f1f2801c 100644
--- a/packages/ui/certd-client/src/views/certd/history/api.ts
+++ b/packages/ui/certd-client/src/views/certd/history/api.ts
@@ -6,7 +6,7 @@ export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
- data: query
+ data: query,
});
}
@@ -14,7 +14,7 @@ export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
- data: obj
+ data: obj,
});
}
@@ -22,7 +22,7 @@ export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
- data: obj
+ data: obj,
});
}
@@ -30,7 +30,7 @@ export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
- params: { id }
+ params: { id },
});
}
@@ -38,7 +38,7 @@ export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
- params: { id }
+ params: { id },
});
}
@@ -46,7 +46,7 @@ export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
- params: { id }
+ params: { id },
});
}
@@ -54,6 +54,6 @@ export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
- data: { ids }
+ data: { ids },
});
}
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/api.ts b/packages/ui/certd-client/src/views/certd/monitor/site/api.ts
index 8121be30..4e85d98f 100644
--- a/packages/ui/certd-client/src/views/certd/monitor/site/api.ts
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/api.ts
@@ -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) {
return await request({
url: apiPrefix + "/disabledChange",
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx
index c3b3e5b1..abba9c88 100644
--- a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx
@@ -8,6 +8,7 @@ import { useSettingStore } from "/@/store/settings";
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
import { mitter } from "/@/utils/util.mitt";
import { useSiteIpMonitor } from "./ip/use";
+import { useSiteImport } from "/@/views/certd/monitor/site/use";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
@@ -44,6 +45,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
+ const { openSiteImportDialog } = useSiteImport();
return {
crudOptions: {
request: {
@@ -97,6 +99,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
await crudExpose.openAdd({});
},
},
+ //导入按钮
+ import: {
+ show: true,
+ text: "批量导入",
+ type: "primary",
+ async click() {
+ openSiteImportDialog({
+ afterSubmit() {
+ crudExpose.doRefresh();
+ },
+ });
+ },
+ },
},
},
rowHandle: {
@@ -235,7 +250,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
};
return (
-
+
{row.certDomains}
);
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/ip/api.ts b/packages/ui/certd-client/src/views/certd/monitor/site/ip/api.ts
index 2cc40972..15c83e5a 100644
--- a/packages/ui/certd-client/src/views/certd/monitor/site/ip/api.ts
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/ip/api.ts
@@ -68,4 +68,11 @@ export const siteIpApi = {
},
});
},
+ async Import(form: any) {
+ return await request({
+ url: apiPrefix + "/import",
+ method: "post",
+ data: form,
+ });
+ },
};
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx
index 3715863e..1f7efe97 100644
--- a/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx
@@ -4,13 +4,11 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { siteIpApi } from "./api";
import dayjs from "dayjs";
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 {
- const { t } = useI18n();
const api = siteIpApi;
- const { crudBinding } = crudExpose;
const pageRequest = async (query: UserPageQuery): Promise => {
if (!query.query) {
query.query = {};
@@ -36,8 +34,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res;
};
- const settingsStore = useSettingStore();
-
const checkStatusDict = dict({
data: [
{ label: "成功", value: "ok", color: "green" },
@@ -45,6 +41,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{ label: "异常", value: "error", color: "red" },
],
});
+ const { openSiteIpImportDialog } = useSiteIpMonitor();
return {
crudOptions: {
request: {
@@ -75,6 +72,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
await crudExpose.openAdd({});
},
},
+ import: {
+ show: true,
+ text: "批量导入",
+ type: "primary",
+ async click() {
+ openSiteIpImportDialog({
+ siteId: context.props.siteId,
+ afterSubmit() {
+ crudExpose.doRefresh();
+ },
+ });
+ },
+ },
load: {
text: "同步IP",
type: "primary",
@@ -295,6 +305,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
data: [
{ label: "同步", value: "sync", color: "green" },
{ label: "手动", value: "manual", color: "blue" },
+ { label: "导入", value: "import", color: "blue" },
],
}),
form: {
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/ip/use.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/ip/use.tsx
index 764cdd51..2a00d4d3 100644
--- a/packages/ui/certd-client/src/views/certd/monitor/site/ip/use.tsx
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/ip/use.tsx
@@ -1,11 +1,10 @@
import { useFormWrapper } from "@fast-crud/fast-crud";
-import { useRouter } from "vue-router";
import SiteIpCertMonitor from "./index.vue";
+import { siteIpApi } from "/@/views/certd/monitor/site/ip/api";
export function useSiteIpMonitor() {
- const { openDialog } = useFormWrapper();
- const router = useRouter();
+ const { openDialog, openCrudFormDialog } = useFormWrapper();
async function openSiteIpMonitorDialog(opts: { siteId: number }) {
await openDialog({
@@ -34,7 +33,42 @@ export function useSiteIpMonitor() {
});
}
+ async function openSiteIpImportDialog(opts: { afterSubmit: any; siteId: any }) {
+ const { afterSubmit } = opts;
+ await openCrudFormDialog({
+ 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 {
openSiteIpMonitorDialog,
+ openSiteIpImportDialog,
};
}
diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/use.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/use.tsx
new file mode 100644
index 00000000..a4b913b7
--- /dev/null
+++ b/packages/ui/certd-client/src/views/certd/monitor/site/use.tsx
@@ -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({
+ 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,
+ };
+}
diff --git a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx
index 46e1dc4a..21c09f75 100644
--- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx
+++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx
@@ -223,6 +223,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
play: {
order: -999,
title: "运行流水线",
+ tooltip: { title: "运行流水线" },
type: "link",
icon: "ant-design:play-circle-outlined",
click({ row }) {
@@ -276,6 +277,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
viewCert: {
order: 3,
title: "查看证书",
+ tooltip: { title: "查看证书" },
type: "link",
icon: "ph:certificate",
async click({ row }) {
@@ -286,6 +288,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
order: 4,
type: "link",
title: "下载证书",
+ tooltip: { title: "下载证书" },
icon: "ant-design:download-outlined",
async click({ row }) {
await downloadCert(row);
diff --git a/packages/ui/certd-client/src/views/certd/pipeline/group/group-selector.vue b/packages/ui/certd-client/src/views/certd/pipeline/group/group-selector.vue
index 48cd2e8b..d370dbcb 100644
--- a/packages/ui/certd-client/src/views/certd/pipeline/group/group-selector.vue
+++ b/packages/ui/certd-client/src/views/certd/pipeline/group/group-selector.vue
@@ -1,7 +1,7 @@
-
+
-
- AI分析
-
-
-
-
- 关闭
- 确定
+ AI分析
+
+ 下载日志
+ 关闭
+
@@ -41,6 +38,7 @@ import { RunHistory } from "../../type";
import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show.vue";
import { usePreferences } from "/@/vben/preferences";
import { useSettingStore } from "/@/store/settings/index";
+import { notification } from "ant-design-vue";
export default {
name: "PiTaskView",
components: { PiStatusShow },
@@ -56,6 +54,22 @@ export default {
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: "关闭",
});
const { isMobile } = usePreferences();
diff --git a/packages/ui/certd-client/src/views/framework/home/dashboard/index.vue b/packages/ui/certd-client/src/views/framework/home/dashboard/index.vue
index a542d407..d5a5e511 100644
--- a/packages/ui/certd-client/src/views/framework/home/dashboard/index.vue
+++ b/packages/ui/certd-client/src/views/framework/home/dashboard/index.vue
@@ -35,6 +35,10 @@
+
+
+
+
diff --git a/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx b/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx
index f851731f..25ff879d 100644
--- a/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx
+++ b/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx
@@ -1,7 +1,9 @@
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 { Modal, notification } from "ant-design-vue";
+import dayjs from "dayjs";
+import { useSettingStore } from "/@/store/settings";
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise => {
@@ -21,6 +23,10 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
const userStore = useUserStore();
+ const settingStore = useSettingStore();
+ const userValidTimeEnabled = compute(() => {
+ return settingStore.sysPublic.userValidTimeEnabled === true;
+ });
return {
crudOptions: {
request: {
@@ -210,6 +216,43 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
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 已过期;
+ }
+ const date = dayjs(value).format("YYYY-MM-DD");
+ return (
+
+
+
+ );
+ },
+ },
+ valueBuilder({ value, row, key }) {
+ if (value != null) {
+ row[key] = dayjs(value);
+ }
+ },
+ valueResolve({ value, row, key }) {
+ if (value != null) {
+ row[key] = value.valueOf();
+ }
+ },
+ },
remark: {
title: "备注",
type: "text",
diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue
index eb9ccec3..8f395eaa 100644
--- a/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue
+++ b/packages/ui/certd-client/src/views/sys/settings/tabs/register.vue
@@ -11,6 +11,13 @@
+
+
+ 有效期内用户可正常使用,失效后流水线将被停用
+
@@ -154,6 +161,14 @@ async function loadSysSettings() {
if (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);
diff --git a/packages/ui/certd-server/.env.dev-mysql.yaml b/packages/ui/certd-server/.env.dev-mysql.yaml
index 93c871ae..f5442116 100644
--- a/packages/ui/certd-server/.env.dev-mysql.yaml
+++ b/packages/ui/certd-server/.env.dev-mysql.yaml
@@ -9,6 +9,6 @@ typeorm:
port: 3309
username: root
password: root
- database: certd2
+ database: certd
diff --git a/packages/ui/certd-server/CHANGELOG.md b/packages/ui/certd-server/CHANGELOG.md
index d30f0b8a..0703043f 100644
--- a/packages/ui/certd-server/CHANGELOG.md
+++ b/packages/ui/certd-server/CHANGELOG.md
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
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)
### Bug Fixes
diff --git a/packages/ui/certd-server/db/migration-mysql/v10024__cname_error.sql b/packages/ui/certd-server/db/migration-mysql/v10024__cname_error.sql
new file mode 100644
index 00000000..2d9efd86
--- /dev/null
+++ b/packages/ui/certd-server/db/migration-mysql/v10024__cname_error.sql
@@ -0,0 +1,3 @@
+
+ALTER TABLE cd_cname_record ADD COLUMN `error` varchar(4096);
+ALTER TABLE sys_user ADD COLUMN `valid_time` bigint;
diff --git a/packages/ui/certd-server/db/migration-pg/v10024__cname_error.sql b/packages/ui/certd-server/db/migration-pg/v10024__cname_error.sql
new file mode 100644
index 00000000..6987ae46
--- /dev/null
+++ b/packages/ui/certd-server/db/migration-pg/v10024__cname_error.sql
@@ -0,0 +1,3 @@
+
+ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
+ALTER TABLE sys_user ADD COLUMN "valid_time" bigint;
diff --git a/packages/ui/certd-server/db/migration/v10024__cname_error.sql b/packages/ui/certd-server/db/migration/v10024__cname_error.sql
new file mode 100644
index 00000000..53312614
--- /dev/null
+++ b/packages/ui/certd-server/db/migration/v10024__cname_error.sql
@@ -0,0 +1,3 @@
+
+ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
+ALTER TABLE sys_user ADD COLUMN "valid_time" integer;
diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json
index 75d47179..0cd8a8b7 100644
--- a/packages/ui/certd-server/package.json
+++ b/packages/ui/certd-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
- "version": "1.34.10",
+ "version": "1.34.11",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -42,20 +42,20 @@
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
- "@certd/acme-client": "^1.34.10",
- "@certd/basic": "^1.34.10",
- "@certd/commercial-core": "^1.34.10",
- "@certd/jdcloud": "^1.34.10",
- "@certd/lib-huawei": "^1.34.10",
- "@certd/lib-k8s": "^1.34.10",
- "@certd/lib-server": "^1.34.10",
- "@certd/midway-flyway-js": "^1.34.10",
- "@certd/pipeline": "^1.34.10",
- "@certd/plugin-cert": "^1.34.10",
- "@certd/plugin-lib": "^1.34.10",
- "@certd/plugin-plus": "^1.34.10",
- "@certd/plus-core": "^1.34.10",
- "@corsinvest/cv4pve-api-javascript": "^8.3.0",
+ "@certd/acme-client": "^1.34.11",
+ "@certd/basic": "^1.34.11",
+ "@certd/commercial-core": "^1.34.11",
+ "@certd/cv4pve-api-javascript": "^8.4.1",
+ "@certd/jdcloud": "^1.34.11",
+ "@certd/lib-huawei": "^1.34.11",
+ "@certd/lib-k8s": "^1.34.11",
+ "@certd/lib-server": "^1.34.11",
+ "@certd/midway-flyway-js": "^1.34.11",
+ "@certd/pipeline": "^1.34.11",
+ "@certd/plugin-cert": "^1.34.11",
+ "@certd/plugin-lib": "^1.34.11",
+ "@certd/plugin-plus": "^1.34.11",
+ "@certd/plus-core": "^1.34.11",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
@@ -105,6 +105,7 @@
"otplib": "^12.0.1",
"pg": "^8.12.0",
"psl": "^1.9.0",
+ "punycode.js": "^2.3.1",
"qiniu": "^7.12.0",
"qrcode": "^1.5.4",
"qs": "^6.13.1",
diff --git a/packages/ui/certd-server/src/controller/user/monitor/site-info-controller.ts b/packages/ui/certd-server/src/controller/user/monitor/site-info-controller.ts
index ae56f156..1d686c0c 100644
--- a/packages/ui/certd-server/src/controller/user/monitor/site-info-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/monitor/site-info-controller.ts
@@ -105,6 +105,17 @@ export class SiteInfoController extends CrudController {
await this.service.checkAllByUsers(userId);
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 })
async ipCheckChange(@Body(ALL) bean: any) {
const userId = this.getUserId();
diff --git a/packages/ui/certd-server/src/controller/user/monitor/site-ip-controller.ts b/packages/ui/certd-server/src/controller/user/monitor/site-ip-controller.ts
index 0bd175c8..713928cb 100644
--- a/packages/ui/certd-server/src/controller/user/monitor/site-ip-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/monitor/site-ip-controller.ts
@@ -111,5 +111,16 @@ export class SiteInfoController extends CrudController {
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();
+ }
+
}
diff --git a/packages/ui/certd-server/src/modules/cname/entity/cname-record.ts b/packages/ui/certd-server/src/modules/cname/entity/cname-record.ts
index 0328f87f..e94e10b6 100644
--- a/packages/ui/certd-server/src/modules/cname/entity/cname-record.ts
+++ b/packages/ui/certd-server/src/modules/cname/entity/cname-record.ts
@@ -26,6 +26,9 @@ export class CnameRecordEntity {
@Column({ comment: '验证状态', length: 20 })
status: string;
+ @Column({ comment: '错误信息' })
+ error: string
+
@Column({
comment: '创建时间',
name: 'create_time',
diff --git a/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts b/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts
index 329a810f..ffaa4bea 100644
--- a/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts
+++ b/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts
@@ -6,13 +6,13 @@ import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.j
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
import {CnameProvider, CnameRecord} from '@certd/pipeline';
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 {CnameProviderEntity} from '../entity/cname-provider.js';
import {CommonDnsProvider} from './common-provider.js';
import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js";
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
-
+import punycode from 'punycode.js'
type CnameCheckCacheValue = {
validating: boolean;
pass: boolean;
@@ -22,11 +22,12 @@ type CnameCheckCacheValue = {
intervalId?: NodeJS.Timeout;
dnsProvider?: IDnsProvider;
};
+
/**
* 授权
*/
@Provide()
-@Scope(ScopeEnum.Request, { allowDowngrade: true })
+@Scope(ScopeEnum.Request, {allowDowngrade: true})
export class CnameRecordService extends BaseService {
@InjectEntityModel(CnameRecordEntity)
repository: Repository;
@@ -47,6 +48,7 @@ export class CnameRecordService extends BaseService {
getRepository() {
return this.repository;
}
+
/**
* 新增
* @param param 数据
@@ -62,7 +64,7 @@ export class CnameRecordService extends BaseService {
param.domain = param.domain.substring(2);
}
param.domain = param.domain.trim()
- const info = await this.getRepository().findOne({ where: { domain: param.domain,userId: param.userId } });
+ const info = await this.getRepository().findOne({where: {domain: param.domain, userId: param.userId}});
if (info) {
return info;
}
@@ -77,17 +79,17 @@ export class CnameRecordService extends BaseService {
} else {
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
}
- await this.cnameProviderChanged(param.userId,param, cnameProvider);
+ await this.cnameProviderChanged(param.userId, param, cnameProvider);
param.status = 'cname';
- const { id } = await super.add(param);
+ const {id} = await super.add(param);
return await this.info(id);
}
- private async cnameProviderChanged(userId:number,param: any, cnameProvider: CnameProviderEntity) {
+ private async cnameProviderChanged(userId: number, param: any, cnameProvider: CnameProviderEntity) {
param.cnameProviderId = cnameProvider.id;
- const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
+ const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
const domainParser = new DomainParser(subDomainGetter);
const realDomain = await domainParser.parse(param.domain);
@@ -117,7 +119,7 @@ export class CnameRecordService extends BaseService {
}
if (old.cnameProviderId !== param.cnameProviderId) {
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
- await this.cnameProviderChanged(old.userId,param, cnameProvider);
+ await this.cnameProviderChanged(old.userId, param, cnameProvider);
param.status = 'cname';
}
return await super.update(param);
@@ -157,10 +159,10 @@ export class CnameRecordService extends BaseService {
if (userId == null) {
throw new ValidateException('userId不能为空');
}
- let record = await this.getRepository().findOne({ where: { domain, userId } });
+ let record = await this.getRepository().findOne({where: {domain, userId}});
if (record == null) {
if (createOnNotFound) {
- record = await this.add({ domain, userId });
+ record = await this.add({domain, userId});
} else {
throw new ValidateException(`找不到${domain}的CNAME记录`);
}
@@ -191,7 +193,7 @@ export class CnameRecordService extends BaseService {
return true;
}
- const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
+ const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
const domainParser = new DomainParser(subDomainGetter);
const cacheKey = `cname.record.verify.${bean.id}`;
@@ -208,7 +210,7 @@ export class CnameRecordService extends BaseService {
if (isDev()) {
ttl = 30 * 1000;
}
- const testRecordValue = 'certd-cname-verify';
+ const testRecordValue = `certd-cname-verify-${bean.id}`;
const buildDnsProvider = async () => {
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
@@ -228,7 +230,7 @@ export class CnameRecordService extends BaseService {
}
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
- const context = { access, logger, http, utils,domainParser };
+ const context = {access, logger, http, utils, domainParser};
const dnsProvider: IDnsProvider = await createDnsProvider({
dnsProviderType: cnameProvider.dnsProviderType,
context,
@@ -239,7 +241,7 @@ export class CnameRecordService extends BaseService {
const clearVerifyRecord = async () => {
cache.delete(cacheKey);
try {
- let dnsProvider =value.dnsProvider
+ let dnsProvider = value.dnsProvider
if (!dnsProvider) {
dnsProvider = await buildDnsProvider();
}
@@ -271,6 +273,9 @@ export class CnameRecordService extends BaseService {
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
+ //检查是否有重复的acme配置
+ await this.checkRepeatAcmeChallengeRecords(fullDomain,bean.recordValue)
+
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
// if (txtRecords.length) {
// records = [].concat(...txtRecords);
@@ -286,7 +291,7 @@ export class CnameRecordService extends BaseService {
if (success) {
clearInterval(value.intervalId);
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
- await this.updateStatus(bean.id, 'valid');
+ await this.updateStatus(bean.id, 'valid', "");
value.pass = true;
await clearVerifyRecord()
return success;
@@ -312,24 +317,102 @@ export class CnameRecordService extends BaseService {
type: 'TXT',
value: testRecordValue,
};
+
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);
value.dnsProvider = dnsProvider;
value.validating = true;
value.recordReq = req;
value.recordRes = recordRes;
- await this.updateStatus(bean.id, 'validating');
+ await this.updateStatus(bean.id, 'validating', "");
value.intervalId = setInterval(async () => {
try {
await checkRecordValue();
} catch (e) {
logger.error('检查cname出错:', e);
+ await this.updateError(bean.id, e.message);
}
}, 10000);
}
- async updateStatus(id: number, status: CnameRecordStatusType) {
- await this.getRepository().update(id, { status });
+ async updateStatus(id: number, status: CnameRecordStatusType, error?: string) {
+ 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}`)
+ }
+ }
+ }
+ }
+
}
}
diff --git a/packages/ui/certd-server/src/modules/cname/service/common-provider.ts b/packages/ui/certd-server/src/modules/cname/service/common-provider.ts
index e9fe32b7..29683151 100644
--- a/packages/ui/certd-server/src/modules/cname/service/common-provider.ts
+++ b/packages/ui/certd-server/src/modules/cname/service/common-provider.ts
@@ -1,6 +1,6 @@
import {CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert';
import {PlusService} from '@certd/lib-server';
-
+import punycode from 'punycode.js'
export type CommonCnameProvider = {
id: number;
domain: string;
@@ -24,6 +24,23 @@ export class CommonDnsProvider implements IDnsProvider {
this.plusService = opts.plusService;
}
+ /**
+ * 中文转英文
+ * @param domain
+ */
+ punyCodeEncode(domain: string) {
+ return punycode.encode(domain);
+ }
+
+ /**
+ * 转中文域名
+ * @param domain
+ */
+ punyCodeDecode(domain: string) {
+ return punycode.decode(domain);
+ }
+
+
usePunyCode(): boolean {
return false
}
diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts
index 183dc359..8886481f 100644
--- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts
+++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts
@@ -1,19 +1,19 @@
-import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
-import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from '@certd/lib-server';
-import { InjectEntityModel } from '@midwayjs/typeorm';
-import { Repository } from 'typeorm';
-import { SiteInfoEntity } from '../entity/site-info.js';
-import { siteTester } from './site-tester.js';
-import dayjs from 'dayjs';
-import { logger, utils } from '@certd/basic';
-import { PeerCertificate } from 'tls';
-import { NotificationService } from '../../pipeline/service/notification-service.js';
-import { isComm, isPlus } from '@certd/plus-core';
-import { UserSuiteService } from '@certd/commercial-core';
+import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
+import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
+import { InjectEntityModel } from "@midwayjs/typeorm";
+import { Repository } from "typeorm";
+import { SiteInfoEntity } from "../entity/site-info.js";
+import { siteTester } from "./site-tester.js";
+import dayjs from "dayjs";
+import { logger, utils } from "@certd/basic";
+import { PeerCertificate } from "tls";
+import { NotificationService } from "../../pipeline/service/notification-service.js";
+import { isComm, isPlus } from "@certd/plus-core";
+import { UserSuiteService } from "@certd/commercial-core";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
-import { UserSiteMonitorSetting } from "../../mine/service/models.js";
-import {SiteIpService} from "./site-ip-service.js";
-import {SiteIpEntity} from "../entity/site-ip.js";
+import { UserSiteMonitorSetting } from "../../mine/service/models.js";
+import { SiteIpService } from "./site-ip-service.js";
+import { SiteIpEntity } from "../entity/site-ip.js";
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -43,7 +43,7 @@ export class SiteInfoService extends BaseService {
async add(data: SiteInfoEntity) {
if (!data.userId) {
- throw new Error('userId is required');
+ throw new Error("userId is required");
}
if (isComm()) {
@@ -51,25 +51,34 @@ export class SiteInfoService extends BaseService {
if (suiteSetting.enabled) {
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
- throw new NeedSuiteException('站点监控数量已达上限,请购买或升级套餐');
+ throw new NeedSuiteException("站点监控数量已达上限,请购买或升级套餐");
}
}
- }else if (!isPlus()) {
- const count = await this.getUserMonitorCount(data.userId);
- if (count >= 1) {
- throw new NeedVIPException('站点监控数量已达上限,请升级专业版');
- }
+ } else if (!isPlus()) {
+ const count = await this.getUserMonitorCount(data.userId);
+ if (count >= 1) {
+ throw new NeedVIPException("站点监控数量已达上限,请升级专业版");
+ }
+ }
+ 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 };
}
-
-
- data.disabled = false;
return await super.add(data);
}
async update(data: any) {
if (!data.id) {
- throw new Error('id is required');
+ throw new Error("id is required");
}
delete data.userId;
await super.update(data);
@@ -77,10 +86,10 @@ export class SiteInfoService extends BaseService {
async getUserMonitorCount(userId: number) {
if (!userId) {
- throw new Error('userId is required');
+ throw new Error("userId is required");
}
return await this.repository.count({
- where: { userId },
+ where: { userId }
});
}
@@ -92,26 +101,26 @@ export class SiteInfoService extends BaseService {
*/
async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) {
if (!site?.domain) {
- throw new Error('站点域名不能为空');
+ throw new Error("站点域名不能为空");
}
try {
await this.update({
id: site.id,
- checkStatus: 'checking',
- lastCheckTime: dayjs().valueOf(),
+ checkStatus: "checking",
+ lastCheckTime: dayjs().valueOf()
});
const res = await siteTester.test({
host: site.domain,
port: site.httpsPort,
- retryTimes,
+ retryTimes
});
const certi: PeerCertificate = res.certificate;
if (!certi) {
- throw new Error('没有发现证书');
+ throw new Error("没有发现证书");
}
const expires = certi.valid_to;
- const allDomains = certi.subjectaltname?.replaceAll('DNS:', '').split(',') ||[];
+ const allDomains = certi.subjectaltname?.replaceAll("DNS:", "").split(",") || [];
const mainDomain = certi.subject?.CN;
let domains = allDomains;
if (!allDomains.includes(mainDomain)) {
@@ -119,23 +128,26 @@ export class SiteInfoService extends BaseService {
}
const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`;
const isExpired = dayjs().valueOf() > dayjs(expires).valueOf();
- const status = isExpired ? 'expired' : 'ok';
+ const status = isExpired ? "expired" : "ok";
const updateData = {
id: site.id,
- certDomains: domains.join(','),
+ certDomains: domains.join(","),
certStatus: status,
certProvider: issuer,
certExpiresTime: dayjs(expires).valueOf(),
lastCheckTime: dayjs().valueOf(),
error: null,
- checkStatus: 'ok',
+ checkStatus: "ok"
};
+ if (site.ipCheck) {
+ delete updateData.checkStatus
+ }
await this.update(updateData);
//检查ip
- await this.checkAllIp(site)
+ await this.checkAllIp(site);
if (!notify) {
return;
@@ -143,15 +155,15 @@ export class SiteInfoService extends BaseService {
try {
await this.sendExpiresNotify(site);
} catch (e) {
- logger.error('send notify error', e);
+ logger.error("send notify error", e);
}
} catch (e) {
- logger.error('check site error', e);
+ logger.error("check site error", e);
await this.update({
id: site.id,
- checkStatus: 'error',
+ checkStatus: "error",
lastCheckTime: dayjs().valueOf(),
- error: e.message,
+ error: e.message
});
if (!notify) {
return;
@@ -159,49 +171,56 @@ export class SiteInfoService extends BaseService {
try {
await this.sendCheckErrorNotify(site);
} catch (e) {
- logger.error('send notify error', e);
+ logger.error("send notify error", e);
}
}
}
- async checkAllIp(site:SiteInfoEntity){
- if( !site.ipCheck){
+ async checkAllIp(site: SiteInfoEntity) {
+ if (!site.ipCheck) {
return;
}
const certExpiresTime = site.certExpiresTime;
- const onFinished = async (list:SiteIpEntity[])=>{
- let errorCount = 0
- let errorMessage = ""
+ const onFinished = async (list: SiteIpEntity[]) => {
+ let errorCount = 0;
+ let errorMessage = "";
for (const item of list) {
if (!item) {
continue;
}
- errorCount++
- if(item.error){
- errorMessage += `${item.ipAddress}:${item.error}; \n`
- }else if(item.certExpiresTime!==certExpiresTime){
- errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`
- }else{
- errorCount--
+ errorCount++;
+ if (item.error) {
+ errorMessage += `${item.ipAddress}:${item.error}; \n`;
+ } else if (item.certExpiresTime !== certExpiresTime) {
+ errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`;
+ } else {
+ errorCount--;
}
}
- if (errorCount<=0){
- return
+ if (errorCount <= 0) {
+ //检查正常
+ await this.update({
+ id: site.id,
+ checkStatus: "ok",
+ error: "",
+ ipErrorCount: 0
+ });
+ return;
}
await this.update({
id: site.id,
- checkStatus: 'error',
+ checkStatus: "error",
error: errorMessage,
- ipErrorCount: errorCount,
- })
+ ipErrorCount: errorCount
+ });
try {
- site = await this.info(site.id)
- await this.sendCheckErrorNotify(site,true);
+ site = await this.info(site.id);
+ await this.sendCheckErrorNotify(site, true);
} 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 {
async check(id: number, notify = false, retryTimes = 3) {
const site = await this.info(id);
if (!site) {
- throw new Error('站点不存在');
+ throw new Error("站点不存在");
}
return await this.doCheck(site, notify, retryTimes);
}
- async sendCheckErrorNotify(site: SiteInfoEntity,fromIpCheck=false) {
- const url = await this.notificationService.getBindUrl('#/certd/monitor/site');
+ async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) {
+ const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
// 发邮件
await this.notificationService.send(
{
@@ -227,22 +246,23 @@ export class SiteInfoService extends BaseService {
logger: logger,
body: {
url,
- title: `站点证书${fromIpCheck?"(IP)":""}检查出错<${site.name}>`,
+ title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
- errorMessage: site.error,
- },
+ errorMessage: site.error
+ }
},
site.userId
);
}
+
async sendExpiresNotify(site: SiteInfoEntity) {
- const tipDays = 10
+ const tipDays = 10;
const expires = site.certExpiresTime;
- const validDays = dayjs(expires).diff(dayjs(), 'day');
- 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 validDays = dayjs(expires).diff(dayjs(), "day");
+ 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`;
if (validDays >= 0 && validDays < tipDays) {
// 发通知
await this.notificationService.send(
@@ -252,8 +272,8 @@ export class SiteInfoService extends BaseService {
body: {
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
content,
- url,
- },
+ url
+ }
},
site.userId
);
@@ -268,7 +288,7 @@ export class SiteInfoService extends BaseService {
content,
url,
errorMessage: "站点证书已过期"
- },
+ }
},
site.userId
);
@@ -277,10 +297,10 @@ export class SiteInfoService extends BaseService {
async checkAllByUsers(userId: any) {
if (!userId) {
- throw new Error('userId is required');
+ throw new Error("userId is required");
}
const sites = await this.repository.find({
- where: { userId },
+ where: { userId }
});
this.checkList(sites);
}
@@ -294,7 +314,7 @@ export class SiteInfoService extends BaseService {
}
}
- async getSetting(userId: number){
+ async getSetting(userId: number) {
return await this.userSettingsService.getSetting(userId, UserSiteMonitorSetting);
}
@@ -302,26 +322,78 @@ export class SiteInfoService extends BaseService {
await this.userSettingsService.saveSetting(userId, bean);
}
- async ipCheckChange(req: {id: any; ipCheck: any}) {
+ async ipCheckChange(req: { id: any; ipCheck: any }) {
await this.update({
id: req.id,
- ipCheck: req.ipCheck,
+ ipCheck: req.ipCheck
});
- if(req.ipCheck){
+ if (req.ipCheck) {
const site = await this.info(req.id);
- await this.siteIpService.sync(site)
+ await this.siteIpService.sync(site);
}
}
async disabledChange(req: { disabled: any; id: any }) {
await this.update({
id: req.id,
- disabled: req.disabled,
+ disabled: req.disabled
});
- if(!req.disabled){
+ if (!req.disabled) {
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);
+ }
}
diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts
index 6c89975f..bf7ba7e8 100644
--- a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts
+++ b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts
@@ -182,7 +182,7 @@ export class SiteIpService extends BaseService {
const finished = res.filter(item=>{
return item!=null
})
- if (finished.length > 0) {
+ if (onFinish) {
onFinish && onFinish(finished)
}
})
@@ -232,4 +232,50 @@ export class SiteIpService extends BaseService {
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);
+ }
}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
index 0f8f4c79..b90340ff 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
@@ -233,17 +233,18 @@ export class PipelineService extends BaseService {
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
}
}
- }
-
- const userId = bean.userId;
- const userIsAdmin = await this.userService.isAdmin(userId);
- if (!userIsAdmin) {
- //非管理员用户,限制pipeline数量
- const count = await this.repository.count({ where: { userId } });
- const sysPublic = await this.sysSettingsService.getSetting(SysPublicSettings);
- const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
- if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
- throw new NeedVIPException(`普通用户最多只能创建${limitUserPipelineCount}条流水线`);
+ }else{
+ //非商业版校验用户最大流水线数量
+ const userId = bean.userId;
+ const userIsAdmin = await this.userService.isAdmin(userId);
+ if (!userIsAdmin) {
+ //非管理员用户,限制pipeline数量
+ const count = await this.repository.count({ where: { userId } });
+ const sysPublic = await this.sysSettingsService.getSetting(SysPublicSettings);
+ const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
+ if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
+ throw new NeedVIPException(`普通用户最多只能创建${limitUserPipelineCount}条流水线`);
+ }
}
}
}
@@ -329,6 +330,7 @@ export class PipelineService extends BaseService {
if (isComm()) {
await this.checkHasDeployCount(id, entity.userId);
}
+ await this.checkUserStatus(entity.userId)
this.cron.register({
name: `pipeline.${id}.trigger.once`,
cron: null,
@@ -446,6 +448,13 @@ export class PipelineService extends BaseService {
if (isComm()) {
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);
if (!pipeline.id) {
@@ -745,5 +754,25 @@ export class PipelineService extends BaseService {
}
-
+ 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)
+ }
+ }
+ }
+ }
}
diff --git a/packages/ui/certd-server/src/modules/sys/authority/entity/user.ts b/packages/ui/certd-server/src/modules/sys/authority/entity/user.ts
index f9c36999..c7deb028 100644
--- a/packages/ui/certd-server/src/modules/sys/authority/entity/user.ts
+++ b/packages/ui/certd-server/src/modules/sys/authority/entity/user.ts
@@ -37,6 +37,10 @@ export class UserEntity {
@Column({ comment: '状态 0:禁用 1:启用', default: 1 })
status: number;
+
+ @Column({ name: 'valid_time', comment: '有效期', nullable: true })
+ validTime: number;
+
@Column({
name: 'create_time',
comment: '创建时间',
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts
index 6b087488..1b0e6135 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts
@@ -7,7 +7,7 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
title: '阿里云-部署证书至OSS',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
- desc: '自动部署域名证书至阿里云OSS',
+ desc: '部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
diff --git a/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts
index 186694fb..3ea917ac 100644
--- a/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts
@@ -26,6 +26,11 @@ export type CloudflareRecord = {
})
export class CloudflareDnsProvider extends AbstractDnsProvider {
access!: CloudflareAccess;
+ usePunyCode(): boolean {
+ //是否使用punycode来添加解析记录
+ //默认都使用原始中文域名来添加
+ return true;
+ }
async onInstance() {
//一些初始化的操作
// 也可以通过ctx成员变量传递context
diff --git a/packages/ui/certd-server/src/plugins/plugin-flex/plugins/plugin-refresh-cert.ts b/packages/ui/certd-server/src/plugins/plugin-flex/plugins/plugin-refresh-cert.ts
index 4a33961a..1feaf560 100644
--- a/packages/ui/certd-server/src/plugins/plugin-flex/plugins/plugin-refresh-cert.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-flex/plugins/plugin-refresh-cert.ts
@@ -91,8 +91,9 @@ export class FlexCDNRefreshCert extends AbstractTaskPlugin {
* timeEndAt: Math.floor((new Date(currentInfo.validTo)).getTime() / 1000),
*
*/
- const commonNames =[ certReader.getMainDomain()]
- const dnsNames = certReader.getAltNames()
+ const topCrt = CertReader.readCertDetail(certReader.cert.ic)
+ const commonNames =[ topCrt.detail.issuer.commonName]
+ const dnsNames = certReader.getAllDomains()
const timeBeginAt = Math.floor(certReader.detail.notBefore.getTime() / 1000);
const timeEndAt = Math.floor(certReader.detail.notAfter.getTime() / 1000);
const body = {
diff --git a/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts b/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts
index 1b85300b..b1c8aa85 100644
--- a/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-github/plugins/plugin-check-release.ts
@@ -77,7 +77,13 @@ export class GithubCheckRelease extends AbstractTaskPlugin {
this.logger.info(`有更新,${lastVersion??"0"}->${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) {
await this.ctx.notificationService.send({
diff --git a/packages/ui/certd-server/src/plugins/plugin-namesilo/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-namesilo/dns-provider.ts
index f0bc6971..693dccb3 100644
--- a/packages/ui/certd-server/src/plugins/plugin-namesilo/dns-provider.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-namesilo/dns-provider.ts
@@ -23,6 +23,11 @@ export class NamesiloDnsProvider extends AbstractDnsProvider {
// 也可以通过ctx成员变量传递context
this.access = this.ctx.access as NamesiloAccess;
}
+ usePunyCode(): boolean {
+ //是否使用punycode来添加解析记录
+ //默认都使用原始中文域名来添加
+ return true;
+ }
private async doRequest(url: string, params: any = null) {
params = merge(
diff --git a/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts b/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts
index 3de4110a..bde99d9a 100644
--- a/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-proxmox/plugins/plugin-upload.ts
@@ -93,7 +93,7 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
async getClient() {
const access: ProxmoxAccess = await this.getAccess(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 login = await client.login(access.username, access.password, access.realm || 'pam');
if (!login) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 48b0646e..3e36e925 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,7 +46,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@@ -72,7 +72,7 @@ importers:
node-forge:
specifier: ^1.3.1
version: 1.3.1
- punycode:
+ punycode.js:
specifier: ^2.3.1
version: 2.3.1
devDependencies:
@@ -207,10 +207,10 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../basic
'@certd/plus-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../pro/plus-core
dayjs:
specifier: ^1.11.7
@@ -415,7 +415,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@@ -455,16 +455,16 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/acme-client
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@certd/plus-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../pro/plus-core
'@midwayjs/cache':
specifier: ~3.14.0
@@ -607,16 +607,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/acme-client
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@certd/plugin-lib':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../plugin-lib
'@google-cloud/publicca':
specifier: ^1.3.0
@@ -633,7 +633,7 @@ importers:
psl:
specifier: ^1.9.0
version: 1.15.0
- punycode:
+ punycode.js:
specifier: ^2.3.1
version: 2.3.1
rimraf:
@@ -682,9 +682,6 @@ importers:
packages/plugins/plugin-lib:
dependencies:
- '@alicloud/credentials':
- specifier: ^2.4.3
- version: 2.4.3
'@alicloud/openapi-client':
specifier: ^0.4.14
version: 0.4.14
@@ -698,10 +695,10 @@ importers:
specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@kubernetes/client-node':
specifier: 0.21.0
@@ -789,19 +786,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/lib-server':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-server
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@certd/plugin-plus':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../plugin-plus
'@certd/plus-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../plus-core
'@midwayjs/core':
specifier: ~3.20.3
@@ -886,22 +883,22 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/lib-k8s':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-k8s
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@certd/plugin-cert':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../plugins/plugin-lib
'@certd/plus-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@@ -1004,7 +1001,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1294,10 +1291,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-iframe
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@@ -1477,47 +1474,47 @@ importers:
specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/acme-client
'@certd/basic':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/basic
'@certd/commercial-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../pro/commercial-core
+ '@certd/cv4pve-api-javascript':
+ specifier: ^8.4.1
+ version: 8.4.1
'@certd/jdcloud':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-k8s
'@certd/lib-server':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../core/pipeline
'@certd/plugin-cert':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../pro/plugin-plus
'@certd/plus-core':
- specifier: ^1.34.7
+ specifier: ^1.34.10
version: link:../../pro/plus-core
- '@corsinvest/cv4pve-api-javascript':
- specifier: ^8.3.0
- version: 8.4.0
'@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120
version: 3.1.149
@@ -1665,6 +1662,9 @@ importers:
psl:
specifier: ^1.9.0
version: 1.15.0
+ punycode.js:
+ specifier: ^2.3.1
+ version: 2.3.1
qiniu:
specifier: ^7.12.0
version: 7.14.0
@@ -2659,13 +2659,13 @@ packages:
'@better-scroll/zoom@2.5.1':
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
+ '@certd/cv4pve-api-javascript@8.4.1':
+ resolution: {integrity: sha512-jxlRieJmCA0Z9LnwX6Ra6ZekRGJEu8o8RGYoKU0Jjkhc9jm6ChEbVyfE7Iw49/hlpA+2yaHdAXb46au/afCISg==}
+
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
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':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -14881,15 +14881,15 @@ snapshots:
dependencies:
'@better-scroll/core': 2.5.1
- '@colors/colors@1.5.0':
- optional: true
-
- '@corsinvest/cv4pve-api-javascript@8.4.0':
+ '@certd/cv4pve-api-javascript@8.4.1':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
+ '@colors/colors@1.5.0':
+ optional: true
+
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -20740,13 +20740,13 @@ snapshots:
resolve: 1.22.10
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:
eslint: 7.32.0
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
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):
dependencies:
@@ -23454,7 +23454,7 @@ snapshots:
eslint: 7.32.0
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-node: 11.1.0(eslint@7.32.0)
- eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@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
inquirer: 7.3.3
json5: 2.2.3