diff --git a/.gitignore b/.gitignore index b9c2e515e..d9e59e533 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -./packages/core/lego +./packages/core/lego # IntelliJ project files node_modules/ npm-debug.log @@ -34,4 +34,8 @@ test.js /logs .pnpm-lock.yaml pnpm-lock.yaml -.studio/ \ No newline at end of file +.studio/ + +# Certd 推广报告,仅本地使用 +/popularize/ + diff --git a/.trae/skills/fast-crud-page-dev/SKILL.md b/.trae/skills/fast-crud-page-dev/SKILL.md index 43b7bbed4..43b2d950b 100644 --- a/.trae/skills/fast-crud-page-dev/SKILL.md +++ b/.trae/skills/fast-crud-page-dev/SKILL.md @@ -25,7 +25,7 @@ version: 1.0.0 ## 实现流程 1. 先在 `packages/ui/certd-client/src/views` 下找 1-2 个相近 Fast Crud 页面,沿用它们的导入、布局、命名和权限写法。 -2. 在 `index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` / `onActivated` 时调用 `crudExpose.doRefresh()`。 +2. 在 `index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` 或 `onActivated` 时调用 `crudExpose.doRefresh()`;两个生命周期同时存在时只保留一个刷新入口,避免首次进入页面请求两次。 3. 在 `crud.tsx` 中配置 `request.pageRequest`、`columns`、`search`、`form`、`rowHandle`、`actionbar`、`toolbar` 等,接口分页参数和返回值按现有页面适配。 4. 操作按钮优先放在 Fast Crud 的 `rowHandle.buttons` 或 `actionbar.buttons` 中;审核、保存设置、批量操作等复杂交互可通过 `context` 调用 `index.vue` 中的方法。 5. 金额、状态、时间、枚举等字段优先复用项目已有组件、字典和格式化工具;避免在模板里重复堆格式化逻辑。 @@ -77,6 +77,84 @@ container:{}, //容器配置 ,对应fs-container - 有固定操作栏、统计区、说明区时,这些区域应 `flex: none`,把剩余空间交给表格区域。 - 修改嵌入式 Fast Crud 页面后,要检查空数据、少量数据和多页数据时表格高度、分页器和空状态是否仍在预期区域内。 +## 列表导出 + +- 列表需要导出时,优先使用 Fast Crud 工具栏导出能力,不要另写一套导出按钮或后端接口,除非数据必须跨权限、跨分页或异步生成文件。 +- 导出当前搜索条件下的数据时,在 `toolbar.export` 中设置 `dataFrom: "search"`,并显式打开导出按钮。 +- 导出列必须输出 Excel 可读的纯文本或数字;不要直接导出对象、数组、VNode、进度条组件、开关组件、时间戳毫秒值等。 +- 有隐藏但业务上需要导出的字段时,把字段定义为普通列并设置 `column.show: false`,再在 `columnFilter` 中对该字段返回 `true`。例如证书域名这类只用于导出的辅助列。 +- 嵌套字段可以使用 `lastVars.certDomains` 这类 key;导出格式化时用安全取值函数读取嵌套值。 +- `dataFormatter` 中统一格式化特殊字段:时间字段转 `YYYY-MM-DD HH:mm:ss`,日期类有效期转业务文案或 `YYYY-MM-DD`,枚举/开关转字典 label,数组转逗号分隔字符串,对象转明确的业务摘要。 + +```typescript +import { ColumnProps, DataFormatterContext } from "@fast-crud/fast-crud"; +import dayjs from "dayjs"; + +function getRecordValue(row: any, key: string) { + return key.split(".").reduce((target, item) => target?.[item], row); +} + +function formatListValue(value: any) { + if (Array.isArray(value)) { + return value.join(","); + } + return value ?? ""; +} + +function exportColumnFilter(col: ColumnProps) { + if (!col.key || ["_index", "_selection", "rowHandle"].includes(col.key)) { + return false; + } + if (col.key === "lastVars.certDomains") { + return true; + } + return col.show !== false; +} + +function exportDataFormatter(opts: DataFormatterContext) { + const { row, originalRow, col, exportCol } = opts; + const key = col.key; + const value = getRecordValue(originalRow, key); + + if (key === "lastVars.certDomains") { + row[key] = formatListValue(value); + } else if (key.includes("Time") && value) { + row[key] = dayjs(value).format("YYYY-MM-DD HH:mm:ss"); + } + + if (col.width) { + exportCol.width = col.width / 10; + } +} + +return { + crudOptions: { + toolbar: { + buttons: { + export: { show: true }, + }, + export: { + dataFrom: "search", + columnFilter: exportColumnFilter, + dataFormatter: exportDataFormatter, + }, + }, + columns: { + "lastVars.certDomains": { + title: "证书域名", + type: "text", + column: { + show: false, + width: 260, + ellipsis: true, + }, + form: { show: false }, + }, + }, + }, +}; +``` + ## 内置 CRUD 按钮 只要在 `request` 中配置了 `addRequest`、`editRequest`、`delRequest`,Fast Crud 会自动在 `rowHandle` 渲染新增、编辑、删除按钮并完成对应操作,**不需要手写 `openDeleteConfirm`、`openEditDialog` 等方法**。 diff --git a/CHANGELOG.md b/CHANGELOG.md index a697acc1c..8e2b57f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,18 @@ - # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7)) +* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9)) +* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0)) +* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc)) +* **volcengine-vke:** 火山VKE集群证书支持两种类型的证书保密字典 ([77b8024](https://github.com/certd/certd/commit/77b802445322d576d54d194f7c505da49e0e824c)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/docs/guide/changelogs/CHANGELOG.md b/docs/guide/changelogs/CHANGELOG.md index a697acc1c..8e2b57f28 100644 --- a/docs/guide/changelogs/CHANGELOG.md +++ b/docs/guide/changelogs/CHANGELOG.md @@ -1,9 +1,18 @@ - # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7)) +* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9)) +* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0)) +* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc)) +* **volcengine-vke:** 火山VKE集群证书支持两种类型的证书保密字典 ([77b8024](https://github.com/certd/certd/commit/77b802445322d576d54d194f7c505da49e0e824c)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 196c7f402..6860dfe8a 100644 --- a/lerna.json +++ b/lerna.json @@ -9,5 +9,5 @@ } }, "npmClient": "pnpm", - "version": "1.41.0" + "version": "1.41.1" } diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md index 1b70c24a5..67d42ae84 100644 --- a/packages/core/acme-client/CHANGELOG.md +++ b/packages/core/acme-client/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.41.1](https://github.com/publishlab/node-acme-client/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/acme-client + # [1.41.0](https://github.com/publishlab/node-acme-client/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index 23128e343..0e80c81eb 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -3,7 +3,7 @@ "description": "Simple and unopinionated ACME client", "private": false, "author": "nmorsman", - "version": "1.41.0", + "version": "1.41.1", "type": "module", "module": "./dist/index.js", "main": "./dist/index.js", @@ -18,7 +18,7 @@ "types" ], "dependencies": { - "@certd/basic": "^1.41.0", + "@certd/basic": "^1.41.1", "@peculiar/x509": "^1.11.0", "asn1js": "^3.0.5", "axios": "^1.9.0", @@ -76,5 +76,5 @@ "bugs": { "url": "https://github.com/publishlab/node-acme-client/issues" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/core/basic/CHANGELOG.md b/packages/core/basic/CHANGELOG.md index e8a351624..1f38e8a58 100644 --- a/packages/core/basic/CHANGELOG.md +++ b/packages/core/basic/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/basic diff --git a/packages/core/basic/build.md b/packages/core/basic/build.md index e4394158c..bb6eb5c27 100644 --- a/packages/core/basic/build.md +++ b/packages/core/basic/build.md @@ -1 +1 @@ -12:22 +02:33 diff --git a/packages/core/basic/package.json b/packages/core/basic/package.json index 495a9431e..0c2bd86c6 100644 --- a/packages/core/basic/package.json +++ b/packages/core/basic/package.json @@ -1,7 +1,7 @@ { "name": "@certd/basic", "private": false, - "version": "1.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -52,5 +52,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/core/basic/src/utils/util.request.test.ts b/packages/core/basic/src/utils/util.request.test.ts index cf66ef4cb..6617fe1b8 100644 --- a/packages/core/basic/src/utils/util.request.test.ts +++ b/packages/core/basic/src/utils/util.request.test.ts @@ -1,8 +1,9 @@ import { expect } from "chai"; -import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js"; +import { createAgent, createAxiosService, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js"; import { ILogger } from "./util.log.js"; const testLogger = { + debug() {}, info() {}, error() {}, } as unknown as ILogger; @@ -10,6 +11,9 @@ const testLogger = { describe("util.request", () => { afterEach(() => { setGlobalHeaders({}); + setGlobalProxy({}); + delete process.env.NO_PROXY; + delete process.env.no_proxy; }); it("should merge global headers without overriding request headers", async () => { @@ -50,4 +54,122 @@ describe("util.request", () => { request: "request", }); }); + + it("should set no_proxy environment variables", () => { + setGlobalProxy({ + httpProxy: "http://127.0.0.1:1080", + httpsProxy: "http://127.0.0.1:1080", + noProxy: "localhost,*.internal.example.com", + }); + + expect(process.env.NO_PROXY).to.equal("localhost,*.internal.example.com"); + expect(process.env.no_proxy).to.equal("localhost,*.internal.example.com"); + }); + + it("should normalize multiline no_proxy environment variables", () => { + setGlobalProxy({ + noProxy: "localhost\n127.0.0.1, 192.168.*\n*.internal.example.com", + }); + + expect(process.env.NO_PROXY).to.equal("localhost,127.0.0.1,192.168.*,*.internal.example.com"); + expect(process.env.no_proxy).to.equal("localhost,127.0.0.1,192.168.*,*.internal.example.com"); + }); + + it("should not change environment variables when creating agents", () => { + process.env.HTTP_PROXY = "http://old-http-proxy"; + process.env.HTTPS_PROXY = "http://old-https-proxy"; + process.env.NO_PROXY = "old.local"; + + createAgent({ + httpProxy: "http://127.0.0.1:1080", + httpsProxy: "http://127.0.0.1:1081", + }); + + expect(process.env.HTTP_PROXY).to.equal("http://old-http-proxy"); + expect(process.env.HTTPS_PROXY).to.equal("http://old-https-proxy"); + expect(process.env.NO_PROXY).to.equal("old.local"); + }); + + it("should bypass global proxy when request host matches no_proxy", async () => { + setGlobalProxy({ + httpProxy: "http://127.0.0.1:1080", + httpsProxy: "http://127.0.0.1:1080", + noProxy: "localhost,.internal.example.com", + }); + + const globalAgents = getGlobalAgents(); + const http = createAxiosService({ logger: testLogger }) as HttpClient; + const res = await http.request({ + url: "https://api.internal.example.com", + method: "get", + logReq: false, + logRes: false, + adapter: async config => { + return { + config, + data: { + usesGlobalHttpAgent: config.httpAgent === globalAgents.httpAgent, + usesGlobalHttpsAgent: config.httpsAgent === globalAgents.httpsAgent, + }, + headers: {}, + status: 200, + statusText: "OK", + }; + }, + }); + + expect(res).to.deep.equal({ + usesGlobalHttpAgent: false, + usesGlobalHttpsAgent: false, + }); + }); + + it("should bypass custom request proxy when request host matches no_proxy", async () => { + setGlobalProxy({ + noProxy: ".internal.example.com", + }); + + const http = createAxiosService({ logger: testLogger }) as HttpClient; + const res = await http.request({ + url: "https://api.internal.example.com", + method: "get", + httpProxy: "http://127.0.0.1:1080", + logReq: false, + logRes: false, + adapter: async config => { + return { + config, + data: { + httpAgent: config.httpAgent?.constructor?.name, + httpsAgent: config.httpsAgent?.constructor?.name, + }, + headers: {}, + status: 200, + statusText: "OK", + }; + }, + }); + + expect(res).to.deep.equal({ + httpAgent: "Agent", + httpsAgent: "Agent", + }); + }); + + it("should match no_proxy rules", () => { + expect(isNoProxyMatched("*", { hostname: "api.example.com", port: "" })).to.equal(true); + expect(isNoProxyMatched("api.example.com", { hostname: "api.example.com", port: "" })).to.equal(true); + expect(isNoProxyMatched("example.com", { hostname: "api.example.com", port: "" })).to.equal(true); + expect(isNoProxyMatched(".example.com", { hostname: "api.example.com", port: "" })).to.equal(true); + expect(isNoProxyMatched("*.example.com", { hostname: "api.example.com", port: "" })).to.equal(true); + expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.1", port: "" })).to.equal(true); + expect(isNoProxyMatched("192.168.*", { hostname: "192.168.1.10", port: "" })).to.equal(true); + expect(isNoProxyMatched("192.168.*", { hostname: "192.169.1.10", port: "" })).to.equal(false); + expect(isNoProxyMatched("[::1]", { hostname: "::1", port: "" })).to.equal(true); + expect(isNoProxyMatched("[::1]:8443", { hostname: "::1", port: "8443" })).to.equal(true); + expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "8443" })).to.equal(true); + expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "443" })).to.equal(false); + expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.2", port: "" })).to.equal(false); + expect(isNoProxyMatched(".example.com", { hostname: "example.org", port: "" })).to.equal(false); + }); }); diff --git a/packages/core/basic/src/utils/util.request.ts b/packages/core/basic/src/utils/util.request.ts index 5c3d81be3..93c800140 100644 --- a/packages/core/basic/src/utils/util.request.ts +++ b/packages/core/basic/src/utils/util.request.ts @@ -82,11 +82,24 @@ export class HttpError extends Error { export const HttpCommonError = HttpError; let defaultAgents = createAgent(); +const directAgents = createAgent(); +let defaultProxyOptions: GlobalProxyOptions = {}; let defaultHeaders: Record = {}; -export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) { +export type GlobalProxyOptions = { + httpProxy?: string; + httpsProxy?: string; + noProxy?: string; +}; + +export function setGlobalProxy(opts: GlobalProxyOptions) { logger.info("setGlobalProxy:", opts); - defaultAgents = createAgent(opts); + defaultProxyOptions = { ...opts }; + defaultAgents = createAgent({ + httpProxy: opts.httpProxy, + httpsProxy: opts.httpsProxy, + }); + setProxyEnvironment(opts); } export function getGlobalAgents() { @@ -137,21 +150,25 @@ export function createAxiosService({ logger }: { logger: ILogger }) { if (config.timeout == null) { config.timeout = 15000; } - let agents = defaultAgents; - if (config.skipSslVerify || config.httpProxy) { - let rejectUnauthorized = true; + const bypassProxy = shouldBypassProxy(config, defaultProxyOptions.noProxy); + const useCustomProxy = !!config.httpProxy && !bypassProxy; + let agents = bypassProxy ? directAgents : defaultAgents; + if (bypassProxy) { + logger.info("命中no_proxy配置,跳过代理:", config.url); + } + if (config.skipSslVerify || useCustomProxy) { + const agentOptions: any = {}; if (config.skipSslVerify) { logger.info("忽略接口请求的SSL校验"); - rejectUnauthorized = false; + agentOptions.rejectUnauthorized = false; } - const proxy: any = {}; - if (config.httpProxy) { + if (useCustomProxy) { logger.info("使用自定义http代理:", config.httpProxy); - proxy.httpProxy = config.httpProxy; - proxy.httpsProxy = config.httpProxy; + agentOptions.httpProxy = config.httpProxy; + agentOptions.httpsProxy = config.httpProxy; } - agents = createAgent({ rejectUnauthorized, ...proxy } as any); + agents = createAgent(agentOptions); } delete config.skipSslVerify; @@ -354,7 +371,7 @@ export type CreateAgentOptions = { httpsProxy?: string; } & nodeHttp.AgentOptions; export function createAgent(opts: CreateAgentOptions = {}) { - opts = merge( + const { httpProxy, httpsProxy, ...agentOptions } = merge( { autoSelectFamily: true, autoSelectFamilyAttemptTimeout: 1000, @@ -364,29 +381,19 @@ export function createAgent(opts: CreateAgentOptions = {}) { ); let httpAgent, httpsAgent; - const httpProxy = opts.httpProxy; if (httpProxy) { - process.env.HTTP_PROXY = httpProxy; - process.env.http_proxy = httpProxy; logger.info("use httpProxy:", httpProxy); - httpAgent = new HttpProxyAgent(httpProxy, opts as any); - merge(httpAgent.options, opts); + httpAgent = new HttpProxyAgent(httpProxy, agentOptions as any); + merge(httpAgent.options, agentOptions); } else { - process.env.HTTP_PROXY = ""; - process.env.http_proxy = ""; - httpAgent = new nodeHttp.Agent(opts); + httpAgent = new nodeHttp.Agent(agentOptions); } - const httpsProxy = opts.httpsProxy; if (httpsProxy) { - process.env.HTTPS_PROXY = httpsProxy; - process.env.https_proxy = httpsProxy; logger.info("use httpsProxy:", httpsProxy); - httpsAgent = new HttpsProxyAgent(httpsProxy, opts as any); - merge(httpsAgent.options, opts); + httpsAgent = new HttpsProxyAgent(httpsProxy, agentOptions as any); + merge(httpsAgent.options, agentOptions); } else { - process.env.HTTPS_PROXY = ""; - process.env.https_proxy = ""; - httpsAgent = new https.Agent(opts); + httpsAgent = new https.Agent(agentOptions); } return { httpAgent, @@ -394,6 +401,145 @@ export function createAgent(opts: CreateAgentOptions = {}) { }; } +function setProxyEnvironment(opts: GlobalProxyOptions = {}) { + setEnvValue("HTTP_PROXY", opts.httpProxy); + setEnvValue("http_proxy", opts.httpProxy); + setEnvValue("HTTPS_PROXY", opts.httpsProxy); + setEnvValue("https_proxy", opts.httpsProxy); + const noProxy = normalizeNoProxyText(opts.noProxy); + setEnvValue("NO_PROXY", noProxy); + setEnvValue("no_proxy", noProxy); +} + +function setEnvValue(key: string, value?: string) { + process.env[key] = value || ""; +} + +function shouldBypassProxy(config: AxiosRequestConfig, noProxy?: string) { + if (!noProxy) { + return false; + } + const target = getRequestTarget(config); + if (!target) { + return false; + } + return splitNoProxyRules(noProxy).some(item => isNoProxyMatched(item, target)); +} + +function getRequestTarget(config: AxiosRequestConfig) { + try { + const baseURL = config.baseURL || undefined; + const url = new URL(config.url || "", baseURL); + return { + hostname: normalizeHost(url.hostname), + port: url.port, + }; + } catch (e) { + return null; + } +} + +export function isNoProxyMatched(rule: string, target: { hostname: string; port: string }) { + if (rule === "*") { + return true; + } + + const normalizedRule = normalizeNoProxyRule(rule); + if (!normalizedRule.host) { + return false; + } + if (normalizedRule.port && normalizedRule.port !== target.port) { + return false; + } + + const host = normalizeHost(target.hostname); + if (normalizedRule.host.includes("*")) { + return wildcardHostMatched(normalizedRule.host, host); + } + if (normalizedRule.host.startsWith("*.")) { + const suffix = normalizedRule.host.substring(1); + return host.endsWith(suffix); + } + if (normalizedRule.host.startsWith(".")) { + return host === normalizedRule.host.substring(1) || host.endsWith(normalizedRule.host); + } + return host === normalizedRule.host || host.endsWith(`.${normalizedRule.host}`); +} + +function normalizeNoProxyRule(rule: string) { + let value = rule.trim().toLowerCase(); + if (value.includes("://")) { + try { + const url = new URL(value); + return { + host: normalizeHost(url.hostname), + port: url.port, + }; + } catch (e) { + return { + host: "", + port: "", + }; + } + } + + let port = ""; + if (value.startsWith("[")) { + const closeIndex = value.indexOf("]"); + const host = value.substring(1, closeIndex); + const rest = value.substring(closeIndex + 1); + if (rest.startsWith(":")) { + port = rest.substring(1); + } + return { + host: normalizeHost(host), + port, + }; + } + + const colonCount = (value.match(/:/g) || []).length; + const portIndex = value.lastIndexOf(":"); + if (colonCount === 1 && portIndex > -1) { + port = value.substring(portIndex + 1); + value = value.substring(0, portIndex); + } + return { + host: normalizeHost(value), + port, + }; +} + +function normalizeHost(host: string) { + let value = host.trim().toLowerCase(); + if (value.startsWith("[") && value.endsWith("]")) { + value = value.substring(1, value.length - 1); + } + return value; +} + +function wildcardHostMatched(rule: string, host: string) { + const pattern = rule.split("*").map(escapeRegExp).join(".*"); + return new RegExp(`^${pattern}$`).test(host); +} + +function escapeRegExp(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function normalizeNoProxyText(noProxy?: string) { + return splitNoProxyRules(noProxy).join(","); +} + +function splitNoProxyRules(noProxy?: string) { + if (!noProxy) { + return []; + } + return noProxy + .split(/[,\s]+/) + .map(item => item.trim()) + .filter(Boolean); +} + export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) { const { http, config, savePath, logger } = req; return safePromise((resolve, reject) => { diff --git a/packages/core/pipeline/CHANGELOG.md b/packages/core/pipeline/CHANGELOG.md index 93f3b8395..454b25d94 100644 --- a/packages/core/pipeline/CHANGELOG.md +++ b/packages/core/pipeline/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/pipeline + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index c2fbc4347..6bd1d266f 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -1,7 +1,7 @@ { "name": "@certd/pipeline", "private": false, - "version": "1.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -19,8 +19,8 @@ "compile": "tsc --skipLibCheck --watch" }, "dependencies": { - "@certd/basic": "^1.41.0", - "@certd/plus-core": "^1.41.0", + "@certd/basic": "^1.41.1", + "@certd/plus-core": "^1.41.1", "dayjs": "^1.11.7", "lodash-es": "^4.17.21", "reflect-metadata": "^0.1.13" @@ -49,5 +49,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-huawei/CHANGELOG.md b/packages/libs/lib-huawei/CHANGELOG.md index 0d8f71cb3..59dc30543 100644 --- a/packages/libs/lib-huawei/CHANGELOG.md +++ b/packages/libs/lib-huawei/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/lib-huawei + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/lib-huawei diff --git a/packages/libs/lib-huawei/package.json b/packages/libs/lib-huawei/package.json index 3a0f8494b..8b18c9cb1 100644 --- a/packages/libs/lib-huawei/package.json +++ b/packages/libs/lib-huawei/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-huawei", "private": false, - "version": "1.41.0", + "version": "1.41.1", "main": "./dist/bundle.js", "module": "./dist/bundle.js", "types": "./dist/d/index.d.ts", @@ -27,5 +27,5 @@ "prettier": "^2.8.8", "tslib": "^2.8.1" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-iframe/CHANGELOG.md b/packages/libs/lib-iframe/CHANGELOG.md index 6771a9e22..e1332fa91 100644 --- a/packages/libs/lib-iframe/CHANGELOG.md +++ b/packages/libs/lib-iframe/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/lib-iframe + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/lib-iframe diff --git a/packages/libs/lib-iframe/package.json b/packages/libs/lib-iframe/package.json index 1b20bff0a..15efb9d68 100644 --- a/packages/libs/lib-iframe/package.json +++ b/packages/libs/lib-iframe/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-iframe", "private": false, - "version": "1.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -34,5 +34,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-jdcloud/CHANGELOG.md b/packages/libs/lib-jdcloud/CHANGELOG.md index 075a0d4ae..91aa09585 100644 --- a/packages/libs/lib-jdcloud/CHANGELOG.md +++ b/packages/libs/lib-jdcloud/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/jdcloud + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/jdcloud diff --git a/packages/libs/lib-jdcloud/package.json b/packages/libs/lib-jdcloud/package.json index 05606e0c6..9974c2178 100644 --- a/packages/libs/lib-jdcloud/package.json +++ b/packages/libs/lib-jdcloud/package.json @@ -1,6 +1,6 @@ { "name": "@certd/jdcloud", - "version": "1.41.0", + "version": "1.41.1", "description": "jdcloud openApi sdk", "main": "./dist/bundle.js", "module": "./dist/bundle.js", @@ -59,5 +59,5 @@ "fetch" ] }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-k8s/CHANGELOG.md b/packages/libs/lib-k8s/CHANGELOG.md index 65e4424a8..f4cec5aba 100644 --- a/packages/libs/lib-k8s/CHANGELOG.md +++ b/packages/libs/lib-k8s/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/lib-k8s + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/lib-k8s diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json index f8f34fdc5..ae5b66135 100644 --- a/packages/libs/lib-k8s/package.json +++ b/packages/libs/lib-k8s/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-k8s", "private": false, - "version": "1.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -19,7 +19,7 @@ "compile": "tsc --skipLibCheck --watch" }, "dependencies": { - "@certd/basic": "^1.41.0", + "@certd/basic": "^1.41.1", "@kubernetes/client-node": "0.21.0" }, "devDependencies": { @@ -36,5 +36,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-server/CHANGELOG.md b/packages/libs/lib-server/CHANGELOG.md index b9abd6d67..6734f805a 100644 --- a/packages/libs/lib-server/CHANGELOG.md +++ b/packages/libs/lib-server/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/packages/libs/lib-server/package.json b/packages/libs/lib-server/package.json index fe4af8451..cb2ecc0ff 100644 --- a/packages/libs/lib-server/package.json +++ b/packages/libs/lib-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/lib-server", - "version": "1.41.0", + "version": "1.41.1", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -29,11 +29,11 @@ ], "license": "AGPL", "dependencies": { - "@certd/acme-client": "^1.41.0", - "@certd/basic": "^1.41.0", - "@certd/pipeline": "^1.41.0", - "@certd/plugin-lib": "^1.41.0", - "@certd/plus-core": "^1.41.0", + "@certd/acme-client": "^1.41.1", + "@certd/basic": "^1.41.1", + "@certd/pipeline": "^1.41.1", + "@certd/plugin-lib": "^1.41.1", + "@certd/plus-core": "^1.41.1", "@midwayjs/cache": "3.14.0", "@midwayjs/core": "3.20.11", "@midwayjs/i18n": "3.20.13", @@ -69,5 +69,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index 58ecf6434..13f701ac4 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -81,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings { httpsProxy? = ''; httpProxy? = ''; + noProxy? = ''; commonHeaders?: string = ''; reverseProxies?: Record = {}; diff --git a/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts b/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts index 7fa835a9c..b76450e4d 100644 --- a/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts +++ b/packages/libs/lib-server/src/system/settings/service/sys-settings-service.ts @@ -165,6 +165,7 @@ export class SysSettingsService extends BaseService { const opts = { httpProxy: privateSetting.httpProxy, httpsProxy: privateSetting.httpsProxy, + noProxy: privateSetting.noProxy, }; setGlobalProxy(opts); setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders)); diff --git a/packages/libs/midway-flyway-js/CHANGELOG.md b/packages/libs/midway-flyway-js/CHANGELOG.md index 4f17f171b..8ebe337d8 100644 --- a/packages/libs/midway-flyway-js/CHANGELOG.md +++ b/packages/libs/midway-flyway-js/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/midway-flyway-js + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/midway-flyway-js diff --git a/packages/libs/midway-flyway-js/package.json b/packages/libs/midway-flyway-js/package.json index 91bb737dd..545400daa 100644 --- a/packages/libs/midway-flyway-js/package.json +++ b/packages/libs/midway-flyway-js/package.json @@ -1,6 +1,6 @@ { "name": "@certd/midway-flyway-js", - "version": "1.41.0", + "version": "1.41.1", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -49,5 +49,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/plugins/plugin-cert/CHANGELOG.md b/packages/plugins/plugin-cert/CHANGELOG.md index b4d0f7577..09ccf5ac8 100644 --- a/packages/plugins/plugin-cert/CHANGELOG.md +++ b/packages/plugins/plugin-cert/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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/plugin-cert + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/plugin-cert diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index a3b54f780..db2f3fc91 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-cert", "private": false, - "version": "1.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -18,10 +18,10 @@ "compile": "tsc --skipLibCheck --watch" }, "dependencies": { - "@certd/acme-client": "^1.41.0", - "@certd/basic": "^1.41.0", - "@certd/pipeline": "^1.41.0", - "@certd/plugin-lib": "^1.41.0", + "@certd/acme-client": "^1.41.1", + "@certd/basic": "^1.41.1", + "@certd/pipeline": "^1.41.1", + "@certd/plugin-lib": "^1.41.1", "psl": "^1.9.0", "punycode.js": "^2.3.1" }, @@ -41,5 +41,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/plugins/plugin-lib/CHANGELOG.md b/packages/plugins/plugin-lib/CHANGELOG.md index fc979c44b..4b6b8ba35 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.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +**Note:** Version bump only for package @certd/plugin-lib + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) **Note:** Version bump only for package @certd/plugin-lib diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 095caebb3..29cd85e43 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.41.0", + "version": "1.41.1", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -23,10 +23,10 @@ "@alicloud/pop-core": "^1.7.10", "@alicloud/tea-util": "^1.4.11", "@aws-sdk/client-s3": "^3.964.0", - "@certd/acme-client": "^1.41.0", - "@certd/basic": "^1.41.0", - "@certd/pipeline": "^1.41.0", - "@certd/plus-core": "^1.41.0", + "@certd/acme-client": "^1.41.1", + "@certd/basic": "^1.41.1", + "@certd/pipeline": "^1.41.1", + "@certd/plus-core": "^1.41.1", "@kubernetes/client-node": "0.21.0", "ali-oss": "^6.22.0", "basic-ftp": "^5.0.5", @@ -61,5 +61,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "d368f9666abf71d7f56891b6cbedeb618b82701c" + "gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f" } diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md index 8c1d47ea4..5420f724b 100644 --- a/packages/ui/certd-client/CHANGELOG.md +++ b/packages/ui/certd-client/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7)) +* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9)) +* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0)) +* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 957691955..b6c990841 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.41.0", + "version": "1.41.1", "private": true, "scripts": { "dev": "vite --open", @@ -106,8 +106,8 @@ "zod-defaults": "^0.1.3" }, "devDependencies": { - "@certd/lib-iframe": "^1.41.0", - "@certd/pipeline": "^1.41.0", + "@certd/lib-iframe": "^1.41.1", + "@certd/pipeline": "^1.41.1", "@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/src/locales/langs/en-US/certd/common.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd/common.ts index 39c78dac4..8b5aa3fdd 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd/common.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd/common.ts @@ -82,6 +82,7 @@ export default { pipelineContent: "Pipeline Content", scheduledTaskCount: "Scheduled Task Count", deployTaskCount: "Deployment Task Count", + certDomains: "Certificate Domains", remainingValidity: "Remaining Validity", effectiveTime: "Effective time", expiryTime: "Expiry Time", diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd/pipeline.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd/pipeline.ts index 90d30c820..df7b9aa88 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd/pipeline.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd/pipeline.ts @@ -41,6 +41,7 @@ export default { pi: { validTime: "Piepline Valid Time", validTimeHelper: "Not filled in means permanent validity", + permanentValid: "Permanent", }, types: { certApply: "Cert Apply", diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-settings.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-settings.ts index cb0089c0f..5a9deec92 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-settings.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-settings.ts @@ -111,6 +111,9 @@ export default { httpsProxyPlaceholder: "http://192.168.1.2:18010/", saveThenTestTitle: "Save first, then click test", httpsProxyHelper: "Usually both proxies are the same, save first then test", + noProxy: "Proxy Bypass", + noProxyPlaceholder: "localhost,127.0.0.1,.example.com,192.168.*", + noProxyHelper: "Configure NO_PROXY. Separate entries with commas, spaces, or line breaks; matched requests bypass the proxy. \nExample: localhost,127.0.0.1,.example.com,192.168.*", dualStackNetwork: "Dual Stack Network", ipv4Priority: "IPv4 Priority", ipv6Priority: "IPv6 Priority", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/common.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/common.ts index c68b5d3fb..0569139c1 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/common.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/common.ts @@ -86,6 +86,7 @@ export default { pipelineContent: "流水线内容", scheduledTaskCount: "定时任务数", deployTaskCount: "部署任务数", + certDomains: "证书域名", remainingValidity: "到期剩余", effectiveTime: "生效时间", expiryTime: "过期时间", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/pipeline.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/pipeline.ts index cb27a9976..d70654a86 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/pipeline.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/pipeline.ts @@ -41,6 +41,7 @@ export default { pi: { validTime: "流水线有效期", validTimeHelper: "不填则为永久有效", + permanentValid: "永久有效", }, types: { certApply: "证书申请", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-settings.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-settings.ts index 2d1f30028..22a3832cb 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-settings.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-settings.ts @@ -108,6 +108,9 @@ export default { httpsProxyPlaceholder: "http://192.168.1.2:18010/", saveThenTestTitle: "保存后,再点击测试", httpsProxyHelper: "一般这两个代理填一样的,保存后再测试", + noProxy: "代理排除", + noProxyPlaceholder: "localhost,127.0.0.1,.example.com,192.168.*", + noProxyHelper: "配置NO_PROXY,多个地址可用英文逗号、空格或换行分隔,命中的请求将不走代理\n例如:localhost,127.0.0.1,.example.com,192.168.*", dualStackNetwork: "双栈网络", ipv4Priority: "IPV4优先", ipv6Priority: "IPV6优先", 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 9fe104818..973bbe4cf 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -82,6 +82,7 @@ export const certdResources = [ isMenu: true, icon: "ion:duplicate-outline", auth: true, + keepAlive: true, }, }, { @@ -282,6 +283,7 @@ export const certdResources = [ meta: { icon: "ion:barcode-outline", auth: true, + keepAlive: true, isMenu: true, }, }, @@ -350,6 +352,7 @@ export const certdResources = [ }, icon: "ion:gift-outline", auth: true, + keepAlive: true, }, }, { diff --git a/packages/ui/certd-client/src/router/source/modules/sys.ts b/packages/ui/certd-client/src/router/source/modules/sys.ts index 766135d24..c042885ba 100644 --- a/packages/ui/certd-client/src/router/source/modules/sys.ts +++ b/packages/ui/certd-client/src/router/source/modules/sys.ts @@ -112,7 +112,7 @@ export const sysResources = [ }, { title: "certd.sysResources.headerMenus", - name: "HeaderMenus", + name: "SettingsHeaderMenus", path: "/sys/settings/header-menus", component: "/sys/settings/header-menus/index.vue", meta: { @@ -128,7 +128,7 @@ export const sysResources = [ }, { title: "certd.sysResources.sysAccess", - name: "SysAccess", + name: "SysAccessManager", path: "/sys/access", component: "/sys/access/index.vue", meta: { @@ -311,7 +311,7 @@ export const sysResources = [ }, icon: "ion:bag-check", permission: "sys:settings:edit", - keepAlive: true, + keepAlive: false, auth: true, }, }, 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 c6ada0f37..ffc5d2057 100644 --- a/packages/ui/certd-client/src/store/settings/api.basic.ts +++ b/packages/ui/certd-client/src/store/settings/api.basic.ts @@ -105,6 +105,7 @@ export type InviteSetting = { export type SysPrivateSetting = { httpProxy?: string; httpsProxy?: string; + noProxy?: string; commonHeaders?: string; reverseProxies?: any; dnsResultOrder?: string; diff --git a/packages/ui/certd-client/src/use/use-mounted.ts b/packages/ui/certd-client/src/use/use-mounted.ts new file mode 100644 index 000000000..047181f36 --- /dev/null +++ b/packages/ui/certd-client/src/use/use-mounted.ts @@ -0,0 +1,24 @@ +import { onActivated, onMounted } from "vue"; + +/** + * 可靠的页面刷新钩子: + * - 如果组件实际被 KeepAlive 缓存命中,由 onActivated 触发 init; + * - 如果没有被缓存,由 onMounted 兜底触发,避免不刷新也不触发 onActivated。 + */ +export function useMounted(init: () => void | Promise) { + let activated = false; + + onActivated(() => { + activated = true; + init(); + }); + + onMounted(() => { + // 让 onActivated 有机会先执行;组件未被 KeepAlive 缓存时 onActivated 不会触发,由这里兜底。 + setTimeout(() => { + if (!activated) { + init(); + } + }); + }); +} diff --git a/packages/ui/certd-client/src/views/certd/access/index.vue b/packages/ui/certd-client/src/views/certd/access/index.vue index 2079c4040..c577e8d37 100644 --- a/packages/ui/certd-client/src/views/certd/access/index.vue +++ b/packages/ui/certd-client/src/views/certd/access/index.vue @@ -11,7 +11,8 @@ diff --git a/packages/ui/certd-client/src/views/certd/cert/dns-persist/index.vue b/packages/ui/certd-client/src/views/certd/cert/dns-persist/index.vue index a877357cb..01dd95ba2 100644 --- a/packages/ui/certd-client/src/views/certd/cert/dns-persist/index.vue +++ b/packages/ui/certd-client/src/views/certd/cert/dns-persist/index.vue @@ -25,7 +25,7 @@ const context: any = { const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context }); onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/index.vue b/packages/ui/certd-client/src/views/certd/cert/domain/index.vue index 0a366f8e7..1b40f43e4 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/index.vue +++ b/packages/ui/certd-client/src/views/certd/cert/domain/index.vue @@ -62,7 +62,7 @@ const handleBatchDelete = () => { // 页面打开后获取列表数据 onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/certd/cname/record/index.vue b/packages/ui/certd-client/src/views/certd/cname/record/index.vue index 5f4f49522..77d35ff24 100644 --- a/packages/ui/certd-client/src/views/certd/cname/record/index.vue +++ b/packages/ui/certd-client/src/views/certd/cname/record/index.vue @@ -62,7 +62,7 @@ const handleBatchDelete = () => { // 页面打开后获取列表数据 onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/certd/history/index.vue b/packages/ui/certd-client/src/views/certd/history/index.vue index 74e298830..815d91e8f 100644 --- a/packages/ui/certd-client/src/views/certd/history/index.vue +++ b/packages/ui/certd-client/src/views/certd/history/index.vue @@ -14,13 +14,13 @@ diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/index.vue b/packages/ui/certd-client/src/views/certd/monitor/cert/index.vue index 6c04a8314..8a46a14bb 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/cert/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/index.vue @@ -11,9 +11,9 @@ diff --git a/packages/ui/certd-client/src/views/certd/monitor/history/index.vue b/packages/ui/certd-client/src/views/certd/monitor/history/index.vue index b7e8f2b9f..13f264fcc 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/history/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/history/index.vue @@ -22,8 +22,8 @@ diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/index.vue b/packages/ui/certd-client/src/views/certd/monitor/site/index.vue index 5acc1db55..8b2b3b6e6 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/site/index.vue @@ -27,8 +27,8 @@ diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/ip/index.vue b/packages/ui/certd-client/src/views/certd/monitor/site/ip/index.vue index c3434a669..c1cb46537 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/ip/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/site/ip/index.vue @@ -5,9 +5,9 @@ diff --git a/packages/ui/certd-client/src/views/certd/notification/index.vue b/packages/ui/certd-client/src/views/certd/notification/index.vue index 614e08c27..befc7efe0 100644 --- a/packages/ui/certd-client/src/views/certd/notification/index.vue +++ b/packages/ui/certd-client/src/views/certd/notification/index.vue @@ -11,7 +11,8 @@ 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 70fb8dc5a..6377a13d0 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx @@ -1,4 +1,4 @@ -import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud"; +import { AddReq, ColumnProps, CreateCrudOptionsProps, CreateCrudOptionsRet, DataFormatterContext, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud"; import { Modal, notification } from "ant-design-vue"; import dayjs from "dayjs"; import { computed, ref } from "vue"; @@ -75,6 +75,17 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply const projectStore = useProjectStore(); const { myProjectDict } = useDicts(); const DEFAULT_WILL_EXPIRE_DAYS = settingStore.sysPublic.defaultWillExpireDays || settingStore.sysPublic.defaultCertRenewDays || 15; + const pipelineTypeDictData = [ + { value: "cert", label: t("certd.types.certApply") }, + { value: "cert_upload", label: t("certd.types.certUpload") }, + { value: "custom", label: t("certd.types.custom") }, + { value: "template", label: t("certd.types.template") }, + { value: "cert_auto", label: t("certd.types.certApply") }, + ]; + const disabledDictData = [ + { value: false, label: t("certd.fields.enabledLabel") }, + { value: true, label: t("certd.fields.disabledLabel") }, + ]; function onDialogOpen(opt: any) { const searchForm = crudExpose.getSearchValidatedFormData(); @@ -84,6 +95,79 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply }; } + function getRecordValue(row: any, key: string) { + return key.split(".").reduce((target, item) => target?.[item], row); + } + + function findDictLabel(data: any[], value: any) { + return data.find(item => item.value === value)?.label ?? value; + } + + function formatValidTime(value: any) { + if (!value || value <= 0) { + return t("certd.pi.permanentValid"); + } + if (value < Date.now()) { + return t("certd.hasExpired"); + } + return dayjs(value).format("YYYY-MM-DD"); + } + + function formatRemainingValidity(lastVars: any) { + const expiresTime = lastVars?.certExpiresTime; + if (!expiresTime) { + return "-"; + } + const leftDays = dayjs(expiresTime).diff(dayjs(), "day"); + if (leftDays < 0) { + return t("certd.hasExpired"); + } + return `${leftDays}${t("certd.days")}`; + } + + function formatListValue(value: any) { + if (Array.isArray(value)) { + return value.join(","); + } + return value ?? ""; + } + + function exportColumnFilter(col: ColumnProps) { + if (!col.key || ["_index", "_selection", "rowHandle"].includes(col.key)) { + return false; + } + if (col.key === "lastVars.certDomains") { + return true; + } + return col.show !== false; + } + + function exportDataFormatter(opts: DataFormatterContext) { + const { row, originalRow, col, exportCol } = opts; + const key = col.key; + const value = getRecordValue(originalRow, key); + + if (key === "validTime") { + row[key] = formatValidTime(value); + } else if (key === "lastVars") { + row[key] = formatRemainingValidity(value); + } else if (key === "lastVars.certDomains") { + row[key] = formatListValue(value); + } else if (key === "status") { + row[key] = statusUtil.get(value)?.label ?? value; + } else if (key === "disabled") { + row[key] = findDictLabel(disabledDictData, value); + } else if (key === "type") { + row[key] = findDictLabel(pipelineTypeDictData, value); + } else if (key.includes("Time") && value) { + row[key] = dayjs(value).format("YYYY-MM-DD HH:mm:ss"); + } + + if (col.width) { + exportCol.width = col.width / 10; + } + } + return { crudOptions: { request: { @@ -178,6 +262,18 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply confirmMessage: t("certd.table.confirmDeleteMessage"), }, }, + toolbar: { + buttons: { + export: { + show: true, + }, + }, + export: { + dataFrom: "search", + columnFilter: exportColumnFilter, + dataFormatter: exportDataFormatter, + }, + }, tabs: { name: "groupId", show: true, @@ -419,6 +515,19 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply width: 150, }, }, + "lastVars.certDomains": { + title: t("certd.fields.certDomains"), + type: "text", + form: { + show: false, + }, + column: { + width: 260, + show: false, + ellipsis: true, + showTitle: true, + }, + }, "lastVars.certEffectiveTime": { title: t("certd.fields.effectiveTime"), search: { @@ -503,10 +612,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply }, }, dict: dict({ - data: [ - { value: false, label: t("certd.fields.enabledLabel") }, - { value: true, label: t("certd.fields.disabledLabel") }, - ], + data: disabledDictData, }), form: { value: false, @@ -563,13 +669,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply col: { span: 2 }, }, dict: dict({ - data: [ - { value: "cert", label: t("certd.types.certApply") }, - { value: "cert_upload", label: t("certd.types.certUpload") }, - { value: "custom", label: t("certd.types.custom") }, - { value: "template", label: t("certd.types.template") }, - { value: "cert_auto", label: t("certd.types.certApply") }, - ], + data: pipelineTypeDictData, }), form: { show: false, @@ -650,7 +750,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply align: "center", cellRender({ value }) { if (!value || value <= 0) { - return "-"; + return t("certd.pi.permanentValid"); } if (value < Date.now()) { return t("certd.hasExpired"); diff --git a/packages/ui/certd-client/src/views/certd/pipeline/group/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/group/index.vue index d83be9422..5a54f40e3 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/group/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/group/index.vue @@ -11,7 +11,8 @@ diff --git a/packages/ui/certd-client/src/views/certd/project/detail/index.vue b/packages/ui/certd-client/src/views/certd/project/detail/index.vue index c2bb178cc..37b7a6784 100644 --- a/packages/ui/certd-client/src/views/certd/project/detail/index.vue +++ b/packages/ui/certd-client/src/views/certd/project/detail/index.vue @@ -28,6 +28,7 @@ diff --git a/packages/ui/certd-client/src/views/certd/project/index.vue b/packages/ui/certd-client/src/views/certd/project/index.vue index 3830104e7..16e164289 100644 --- a/packages/ui/certd-client/src/views/certd/project/index.vue +++ b/packages/ui/certd-client/src/views/certd/project/index.vue @@ -53,7 +53,7 @@ const handleBatchDelete = () => { // 页面打开后获取列表数据 onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/certd/suite/mine/index.vue b/packages/ui/certd-client/src/views/certd/suite/mine/index.vue index 8b11383c4..f3f64f7e9 100644 --- a/packages/ui/certd-client/src/views/certd/suite/mine/index.vue +++ b/packages/ui/certd-client/src/views/certd/suite/mine/index.vue @@ -15,14 +15,14 @@ diff --git a/packages/ui/certd-client/src/views/certd/trade/index.vue b/packages/ui/certd-client/src/views/certd/trade/index.vue index a0a2fd8da..631468cf3 100644 --- a/packages/ui/certd-client/src/views/certd/trade/index.vue +++ b/packages/ui/certd-client/src/views/certd/trade/index.vue @@ -19,7 +19,7 @@ const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions }); // 页面打开后获取列表数据 onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/sys/access/index.vue b/packages/ui/certd-client/src/views/sys/access/index.vue index 919916f70..784613453 100644 --- a/packages/ui/certd-client/src/views/sys/access/index.vue +++ b/packages/ui/certd-client/src/views/sys/access/index.vue @@ -11,7 +11,8 @@ diff --git a/packages/ui/certd-client/src/views/sys/pipeline/crud.tsx b/packages/ui/certd-client/src/views/sys/pipeline/crud.tsx index 956f2bdc5..dba04a87e 100644 --- a/packages/ui/certd-client/src/views/sys/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/sys/pipeline/crud.tsx @@ -1,5 +1,5 @@ import createCrudOptionsUser from "/@/views/sys/authority/user/crud"; -import { CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { ColumnProps, CreateCrudOptionsProps, CreateCrudOptionsRet, DataFormatterContext, DelReq, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { message, Modal } from "ant-design-vue"; import dayjs from "dayjs"; import { ref } from "vue"; @@ -18,6 +18,77 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }; const selectedRowKeys = ref([]); + const pipelineTypeDictData = [ + { value: "cert", label: "证书申请" }, + { value: "cert_upload", label: "证书上传" }, + { value: "custom", label: "自定义" }, + { value: "template", label: "模板" }, + { value: "cert_auto", label: "证书申请" }, + ]; + const disabledDictData = [ + { label: "启用", value: false, color: "green" }, + { label: "禁用", value: true, color: "red" }, + ]; + + function findDictLabel(data: any[], value: any) { + return data.find(item => item.value === value)?.label ?? value; + } + + function formatValidTime(value: any) { + if (!value || value <= 0) { + return "永久有效"; + } + if (value < Date.now()) { + return "已过期"; + } + return dayjs(value).format("YYYY-MM-DD"); + } + + function getRecordValue(row: any, key: string) { + return key.split(".").reduce((target, item) => target?.[item], row); + } + + function formatListValue(value: any) { + if (Array.isArray(value)) { + return value.join(","); + } + return value ?? ""; + } + + function exportColumnFilter(col: ColumnProps) { + if (!col.key || ["_index", "_selection", "rowHandle"].includes(col.key)) { + return false; + } + if (col.key === "lastVars.certDomains") { + return true; + } + return col.show !== false; + } + + function exportDataFormatter(opts: DataFormatterContext) { + const { row, originalRow, col, exportCol } = opts; + const key = col.key; + const value = getRecordValue(originalRow, key); + + if (key === "validTime") { + row[key] = formatValidTime(value); + } else if (key === "lastVars.certDomains") { + row[key] = formatListValue(value); + } else if (key === "status") { + row[key] = statusUtil.get(value)?.label ?? value; + } else if (key === "disabled") { + row[key] = findDictLabel(disabledDictData, value); + } else if (key === "type") { + row[key] = findDictLabel(pipelineTypeDictData, value); + } else if (key.includes("Time") && value) { + row[key] = dayjs(value).format("YYYY-MM-DD HH:mm:ss"); + } + + if (col.width) { + exportCol.width = col.width / 10; + } + } + const handleBatchDelete = () => { if (!selectedRowKeys.value?.length) { message.error("请先选择要删除的记录"); @@ -54,6 +125,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, export: { dataFrom: "search", + columnFilter: exportColumnFilter, + dataFormatter: exportDataFormatter, }, }, pagination: { @@ -185,13 +258,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, }, dict: dict({ - data: [ - { value: "cert", label: "证书申请" }, - { value: "cert_upload", label: "证书上传" }, - { value: "custom", label: "自定义" }, - { value: "template", label: "模板" }, - { value: "cert_auto", label: "证书申请" }, - ], + data: pipelineTypeDictData, }), column: { width: 110, @@ -236,10 +303,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, }, dict: dict({ - data: [ - { label: "启用", value: false, color: "green" }, - { label: "禁用", value: true, color: "red" }, - ], + data: disabledDictData, }), column: { width: 90, @@ -273,6 +337,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat show: false, }, }, + "lastVars.certDomains": { + title: "证书域名", + type: "text", + column: { + width: 260, + show: false, + ellipsis: true, + }, + form: { + show: false, + }, + }, lastHistoryTime: { title: "最后执行时间", type: "datetime", @@ -306,7 +382,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat align: "center", cellRender({ value }) { if (!value || value <= 0) { - return "-"; + return "永久有效"; } if (value < Date.now()) { return 已过期; diff --git a/packages/ui/certd-client/src/views/sys/pipeline/index.vue b/packages/ui/certd-client/src/views/sys/pipeline/index.vue index 58bdb647b..e3b001b22 100644 --- a/packages/ui/certd-client/src/views/sys/pipeline/index.vue +++ b/packages/ui/certd-client/src/views/sys/pipeline/index.vue @@ -14,8 +14,8 @@ diff --git a/packages/ui/certd-client/src/views/sys/settings/header-menus/index.vue b/packages/ui/certd-client/src/views/sys/settings/header-menus/index.vue index 399b6e759..e87304a6a 100644 --- a/packages/ui/certd-client/src/views/sys/settings/header-menus/index.vue +++ b/packages/ui/certd-client/src/views/sys/settings/header-menus/index.vue @@ -21,7 +21,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions const settingStore = useSettingStore(); // 页面打开后获取列表数据 onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/network.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/network.vue index b9946267a..6fb163365 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/network.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/network.vue @@ -14,6 +14,11 @@
{{ t("certd.httpsProxyHelper") }}
+ + +
{{ t("certd.noProxyHelper") }}
+
+
{{ t("certd.sys.setting.environmentVarsHelper") }}
diff --git a/packages/ui/certd-client/src/views/sys/site/index.vue b/packages/ui/certd-client/src/views/sys/site/index.vue index 62719a267..7fdb8b4f8 100644 --- a/packages/ui/certd-client/src/views/sys/site/index.vue +++ b/packages/ui/certd-client/src/views/sys/site/index.vue @@ -123,4 +123,12 @@ const loginLogoCropperOptions = ref({ border-radius: 0 !important; margin-top: 0 !important; } + +.page-sys-site { + .sys-settings-form { + width: 900px; + max-width: 100%; + padding: 20px; + } +} diff --git a/packages/ui/certd-client/src/views/sys/suite/activation-code/index.vue b/packages/ui/certd-client/src/views/sys/suite/activation-code/index.vue index 359112f35..a5af3d79d 100644 --- a/packages/ui/certd-client/src/views/sys/suite/activation-code/index.vue +++ b/packages/ui/certd-client/src/views/sys/suite/activation-code/index.vue @@ -16,13 +16,13 @@ import { useFs } from "@fast-crud/fast-crud"; import createCrudOptions from "./crud"; defineOptions({ - name: "ProductActivationCodeManager", + name: "SysProductActivationCode", }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions }); onMounted(() => { - crudExpose.doRefresh(); + // crudExpose.doRefresh(); }); onActivated(async () => { await crudExpose.doRefresh(); diff --git a/packages/ui/certd-client/src/views/sys/suite/invite/level.vue b/packages/ui/certd-client/src/views/sys/suite/invite/level.vue index ae4f2cbea..bba570b90 100644 --- a/packages/ui/certd-client/src/views/sys/suite/invite/level.vue +++ b/packages/ui/certd-client/src/views/sys/suite/invite/level.vue @@ -46,12 +46,13 @@ diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue b/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue index 341b8e459..729007e61 100644 --- a/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue +++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue @@ -11,7 +11,7 @@ diff --git a/packages/ui/certd-server/CHANGELOG.md b/packages/ui/certd-server/CHANGELOG.md index 4d87aba07..d585a0b52 100644 --- a/packages/ui/certd-server/CHANGELOG.md +++ b/packages/ui/certd-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05) + +### Performance Improvements + +* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7)) +* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0)) +* **volcengine-vke:** 火山VKE集群证书支持两种类型的证书保密字典 ([77b8024](https://github.com/certd/certd/commit/77b802445322d576d54d194f7c505da49e0e824c)) + # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) ### Bug Fixes diff --git a/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml b/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml index 628d0b716..33c2bb84d 100644 --- a/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml +++ b/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml @@ -7,6 +7,7 @@ title: 火山引擎-替换VKE证书 icon: svg:icon-volcengine group: volcengine desc: 替换火山引擎VKE集群中的TLS Secret证书 +needPlus: true input: cert: title: 域名证书 @@ -15,6 +16,7 @@ input: name: output-selector from: - ':cert:' + - VolcengineUploadToCertCenter required: true order: 0 certDomains: @@ -118,11 +120,11 @@ input: component: name: a-select options: - - label: 按Ingress替换 - value: ingress - label: 按Secret替换 value: secret - value: ingress + - label: 按Ingress替换 + value: ingress + value: secret required: true order: 0 ingressName: @@ -140,23 +142,37 @@ input: secretName: title: Secret名称 required: true - helper: 存储TLS证书的Secret名称,可填写多个 + helper: 选择要替换的Secret,可多选 component: - name: a-select + name: remote-select vModel: value mode: tags - open: false + type: plugin + action: onGetSecretList + search: false + pager: false + single: false + watches: + - certDomains + - accessId + - regionId + - clusterId + - kubeconfigType + - namespace mergeScript: |2- return { show: ctx.compute(({form}) => form.targetType === 'secret'), - required: ctx.compute(({form}) => form.targetType === 'secret') + required: ctx.compute(({form}) => form.targetType === 'secret'), + component: { + form: ctx.compute(({form}) => form) + } } order: 0 createOnNotFound: title: Secret自动创建 - helper: 如果Secret不存在,则创建kubernetes.io/tls类型Secret + helper: 如果Secret不存在,则创建Opaque类型Secret value: false component: name: a-switch diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index b67aab823..de842896f 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.41.0", + "version": "1.41.1", "description": "fast-server base midway", "private": true, "type": "module", @@ -54,20 +54,20 @@ "@aws-sdk/client-sts": "^3.990.0", "@azure/arm-dns": "^5.1.0", "@azure/identity": "^4.13.1", - "@certd/acme-client": "^1.41.0", - "@certd/basic": "^1.41.0", - "@certd/commercial-core": "^1.41.0", + "@certd/acme-client": "^1.41.1", + "@certd/basic": "^1.41.1", + "@certd/commercial-core": "^1.41.1", "@certd/cv4pve-api-javascript": "^8.4.2", - "@certd/jdcloud": "^1.41.0", - "@certd/lib-huawei": "^1.41.0", - "@certd/lib-k8s": "^1.41.0", - "@certd/lib-server": "^1.41.0", - "@certd/midway-flyway-js": "^1.41.0", - "@certd/pipeline": "^1.41.0", - "@certd/plugin-cert": "^1.41.0", - "@certd/plugin-lib": "^1.41.0", - "@certd/plugin-plus": "^1.41.0", - "@certd/plus-core": "^1.41.0", + "@certd/jdcloud": "^1.41.1", + "@certd/lib-huawei": "^1.41.1", + "@certd/lib-k8s": "^1.41.1", + "@certd/lib-server": "^1.41.1", + "@certd/midway-flyway-js": "^1.41.1", + "@certd/pipeline": "^1.41.1", + "@certd/plugin-cert": "^1.41.1", + "@certd/plugin-lib": "^1.41.1", + "@certd/plugin-plus": "^1.41.1", + "@certd/plus-core": "^1.41.1", "@google-cloud/dns": "^5.3.1", "@google-cloud/publicca": "^1.3.0", "@huaweicloud/huaweicloud-sdk-cdn": "3.1.185", diff --git a/packages/ui/certd-server/src/plugins/plugin-aws/libs/aws-client.ts b/packages/ui/certd-server/src/plugins/plugin-aws/libs/aws-client.ts index 767ea577e..0ea42d074 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aws/libs/aws-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aws/libs/aws-client.ts @@ -30,16 +30,23 @@ export class AwsClient { }, }); - const cert = certInfo.crt.split("-----END CERTIFICATE-----")[0] + "-----END CERTIFICATE-----"; + // Split the full PEM chain: first block is the leaf cert, the rest is the intermediate chain + const pemBlocks = certInfo.crt.split(/(?<=-----END CERTIFICATE-----)/); + const cert = pemBlocks[0].trim(); + const chain = pemBlocks + .slice(1) + .join("") + .trim(); + // 构建上传参数 const data = await acmClient.send( new ImportCertificateCommand({ Certificate: Buffer.from(cert), PrivateKey: Buffer.from(certInfo.key), - // CertificateChain: certificateChain, // 可选 + CertificateChain: chain ? Buffer.from(chain) : undefined, }) ); - console.log("Upload successful:", data); + this.logger.info(`Upload successful: ${data.CertificateArn}`); // 返回证书 ARN(Amazon Resource Name) return data.CertificateArn; } diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts index d2335c44c..a1990434a 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/base-convert.ts @@ -102,6 +102,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { this._result.pipelineVars.certEffectiveTime = dayjs(certReader.detail.notBefore).valueOf(); this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf(); + this._result.pipelineVars.certDomains = certReader.getAllDomains(); if (!this._result.pipelinePrivateVars) { this._result.pipelinePrivateVars = {}; } diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.test.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.test.ts index 748ca73dc..32bd28a07 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.test.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import { VolcengineDeployToVKE } from "./plugin-deploy-to-vke.js"; +import { CertInfo } from "@certd/plugin-cert"; describe("VolcengineDeployToVKE", () => { it("uses a single-select cluster field", () => { @@ -86,4 +87,65 @@ describe("VolcengineDeployToVKE", () => { /当前命名空间可用Ingress:app-web,api-web/ ); }); + + it("creates Secret with cert_center format for new non-tls secrets", async () => { + const plugin = new VolcengineDeployToVKE(); + plugin.namespace = "default"; + plugin.targetType = "secret"; + plugin.secretName = "test-tls"; + plugin.createOnNotFound = true; + plugin.logger = { info: () => undefined } as any; + plugin.appendTimeSuffix = (s: string) => s + "-test"; + + let secretBody: any; + await (plugin as any).patchCertSecret({ + certId: "cert-abc123", + k8sClient: { + patchSecret: async (opts: any) => { + secretBody = opts.body; + return {}; + }, + client: { + readNamespacedSecret: async () => { + throw Object.assign(new Error("Not Found"), { response: { body: { code: 404 } } }); + }, + }, + }, + secretNames: ["test-tls"], + }); + + assert.equal(secretBody.type, "Opaque"); + assert.equal(secretBody.data["cert_id"], Buffer.from("cert-abc123").toString("base64")); + assert.equal(secretBody.data["cert_source"], Buffer.from("cert_center").toString("base64")); + }); + + it("uses tls.crt/tls.key format for kubernetes.io/tls secrets", async () => { + const plugin = new VolcengineDeployToVKE(); + plugin.namespace = "default"; + plugin.targetType = "secret"; + plugin.secretName = "test-tls"; + plugin.logger = { info: () => undefined } as any; + plugin.appendTimeSuffix = (s: string) => s + "-test"; + plugin.cert = { crt: "MY_CRT", key: "MY_KEY" } as CertInfo; + + let secretBody: any; + await (plugin as any).patchCertSecret({ + certId: "cert-abc123", + k8sClient: { + patchSecret: async (opts: any) => { + secretBody = opts.body; + return {}; + }, + client: { + readNamespacedSecret: async () => ({ + body: { type: "kubernetes.io/tls" }, + }), + }, + }, + secretNames: ["test-tls"], + }); + + assert.equal(secretBody.data["tls.crt"], Buffer.from("MY_CRT").toString("base64")); + assert.equal(secretBody.data["tls.key"], Buffer.from("MY_KEY").toString("base64")); + }); }); diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.ts index 35377e1b0..ae6246080 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-vke.ts @@ -1,9 +1,10 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; -import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { utils } from "@certd/basic"; +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-plus"; import { VolcengineAccess } from "../access.js"; import { VolcengineClient } from "../ve-client.js"; -import { utils } from "@certd/basic"; const regionOptions = [ { label: "北京", value: "cn-beijing" }, @@ -20,23 +21,24 @@ const regionOptions = [ icon: "svg:icon-volcengine", group: pluginGroups.volcengine.key, desc: "替换火山引擎VKE集群中的TLS Secret证书", + needPlus:true, default: { strategy: { runStrategy: RunStrategy.SkipWhenSucceed, }, }, }) -export class VolcengineDeployToVKE extends AbstractTaskPlugin { +export class VolcengineDeployToVKE extends AbstractPlusTaskPlugin { @TaskInput({ title: "域名证书", helper: "请选择前置任务输出的域名证书", component: { name: "output-selector", - from: [...CertApplyPluginNames], + from: [...CertApplyPluginNames, "VolcengineUploadToCertCenter"], }, required: true, }) - cert!: CertInfo; + cert!: CertInfo | string; @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) certDomains!: string[]; @@ -107,11 +109,11 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { component: { name: "a-select", options: [ - { label: "按Ingress替换", value: "ingress" }, { label: "按Secret替换", value: "secret" }, + { label: "按Ingress替换", value: "ingress" }, ], }, - value: "ingress", + value: "secret", required: true, }) targetType!: "ingress" | "secret"; @@ -132,17 +134,25 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { @TaskInput({ title: "Secret名称", required: true, - helper: "存储TLS证书的Secret名称,可填写多个", + helper: "选择要替换的Secret,可多选", component: { - name: "a-select", + name: "remote-select", vModel: "value", mode: "tags", - open: false, + type: "plugin", + action: "onGetSecretList", + search: false, + pager: false, + single: false, + watches: ["certDomains", "accessId", "regionId", "clusterId", "kubeconfigType", "namespace"], }, mergeScript: ` return { show: ctx.compute(({form}) => form.targetType === 'secret'), - required: ctx.compute(({form}) => form.targetType === 'secret') + required: ctx.compute(({form}) => form.targetType === 'secret'), + component: { + form: ctx.compute(({form}) => form) + } } `, }) @@ -150,7 +160,7 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { @TaskInput({ title: "Secret自动创建", - helper: "如果Secret不存在,则创建kubernetes.io/tls类型Secret", + helper: "如果Secret不存在,则创建Opaque类型Secret", value: false, component: { name: "a-switch", @@ -181,6 +191,23 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { this.logger.info("开始替换火山引擎VKE证书"); const access = await this.getAccess(this.accessId); const vkeService = await this.getVkeService(access); + + // 上传证书到证书中心 + let certId: string; + if (typeof this.cert !== "string") { + const certInfo = this.cert as CertInfo; + this.logger.info("开始上传证书到证书中心"); + const certService = await this.getCertService(access); + certId = await certService.ImportCertificate({ + certName: this.appendTimeSuffix("certd"), + cert: certInfo, + }); + this.logger.info("上传证书到证书中心成功:" + certId); + } else { + certId = this.cert; + this.logger.info("使用已有证书中心ID:" + certId); + } + const kubeconfigId = await this.createKubeconfig(vkeService); try { @@ -191,7 +218,7 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { skipTLSVerify: this.skipTLSVerify, }); const secretNames = await this.getTargetSecretNames(k8sClient); - await this.patchCertSecret({ cert: this.cert, k8sClient, secretNames }); + await this.patchCertSecret({ certId, k8sClient, secretNames }); } catch (e) { if (e.response?.body) { throw new Error(this.formatK8sError(e.response.body)); @@ -205,6 +232,15 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { this.logger.info("VKE证书替换完成"); } + private async getCertService(access: VolcengineAccess) { + const client = new VolcengineClient({ + logger: this.logger, + access, + http: this.http, + }); + return await client.getCertCenterService(); + } + private async getVkeService(access: VolcengineAccess) { const client = new VolcengineClient({ logger: this.logger, @@ -293,6 +329,11 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { } private formatK8sError(body: any) { + if (body?.code === 422 && body?.message?.includes("field is immutable")) { + const secretName = body.details?.name || "未知"; + return `Secret类型不可变:Secret ${secretName} 已是kubernetes.io/tls类型,type字段不可修改。\n请删除该Secret后重试,或选择正确的Secret。\n原始错误:${JSON.stringify(body)}`; + } + if (body?.code !== 403 || body?.reason !== "Forbidden") { return JSON.stringify(body); } @@ -334,34 +375,71 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { return secretNames; } - private async patchCertSecret(options: { cert: CertInfo; k8sClient: any; secretNames: string[] }) { - const { cert, k8sClient, secretNames } = options; + private async patchCertSecret(options: { certId: string; k8sClient: any; secretNames: string[] }) { + const { certId, k8sClient, secretNames } = options; if (!secretNames || secretNames.length === 0) { throw new Error("Secret名称不能为空"); } - const body: any = { - data: { - "tls.crt": Buffer.from(cert.crt).toString("base64"), - "tls.key": Buffer.from(cert.key).toString("base64"), - }, - metadata: { - labels: { - certd: this.appendTimeSuffix("certd"), - }, - }, - }; - for (const secretName of secretNames) { + let useTlsFormat = false; + try { + const res = await k8sClient.client.readNamespacedSecret(secretName, this.namespace); + useTlsFormat = res.body?.type === "kubernetes.io/tls"; + } catch (e) { + // Secret 不存在,将走创建逻辑 + } + + let body: any; + if (useTlsFormat) { + let crt: string; + let key: string; + if (typeof this.cert === "string") { + const access = await this.getAccess(this.accessId); + const certService = await this.getCertService(access); + const detail = await certService.GetCertificateDetail(this.cert); + crt = detail.CertificateChain || ""; + key = detail.PrivateKey || ""; + this.logger.info("从证书中心获取证书详情成功"); + } else { + crt = this.cert.crt; + key = this.cert.key; + } + body = { + data: { + "tls.crt": Buffer.from(crt).toString("base64"), + "tls.key": Buffer.from(key).toString("base64"), + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + } else { + body = { + type: "Opaque", + data: { + cert_id: Buffer.from(certId).toString("base64"), + cert_source: Buffer.from("cert_center").toString("base64"), + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + } + body.metadata.name = secretName; - this.logger.info(`开始更新VKE Secret:${secretName}`); + this.logger.info("开始更新VKE Secret:" + secretName); await k8sClient.patchSecret({ namespace: this.namespace, secretName, body, createOnNotFound: this.createOnNotFound, }); - this.logger.info(`VKE Secret已更新:${secretName}`); + this.logger.info("VKE Secret已更新:" + secretName); } if (this.targetType === "ingress" && this.ingressName) { @@ -389,6 +467,35 @@ export class VolcengineDeployToVKE extends AbstractTaskPlugin { value: item.Id, })); } + async onGetSecretList() { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + if (!this.clusterId) { + throw new Error("请选择VKE集群"); + } + const access = await this.getAccess(this.accessId); + const vkeService = await this.getVkeService(access); + const kubeconfigId = await this.createKubeconfig(vkeService); + + try { + const kubeconfig = await this.getKubeconfig(vkeService, kubeconfigId); + const k8sClient = new this.K8sClient({ + kubeConfigStr: kubeconfig, + logger: this.logger, + skipTLSVerify: this.skipTLSVerify, + }); + const res = await k8sClient.getSecrets({ namespace: this.namespace || "default" }); + const list = res.body?.items || res.items || []; + return list.map((item: any) => ({ + label: item.metadata.name, + value: item.metadata.name, + })); + } finally { + await this.deleteKubeconfig(vkeService, kubeconfigId); + } + } + } -new VolcengineDeployToVKE(); +new VolcengineDeployToVKE(); \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts index c07bdf1f8..e3049e3c1 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts @@ -1,4 +1,4 @@ -import { VolcengineAccess } from "./access.js"; +import { VolcengineAccess } from "./access.js"; import { HttpClient, ILogger } from "@certd/basic"; export type VolcengineOpts = { diff --git a/trigger/build.trigger b/trigger/build.trigger index de349528c..57d6f91b7 100644 --- a/trigger/build.trigger +++ b/trigger/build.trigger @@ -1 +1 @@ -12:32 +02:38 diff --git a/trigger/release.trigger b/trigger/release.trigger index 2627cc782..ba5cdb488 100644 --- a/trigger/release.trigger +++ b/trigger/release.trigger @@ -1 +1 @@ -15:40 +03:06