Compare commits

...

30 Commits

Author SHA1 Message Date
xiaojunnuo 8d9870e9c6 Merge branch 'v2' into v2-dev 2026-06-09 23:10:45 +08:00
HINS 0f3f8519e0 perf: 优化 HiPM DNSMgr 插件,添加域名查询双层策略 (#744) @WUHINS
- 新增 getDomainId() 方法,首选 keyword 直接查询(O(1))
- 列表匹配作为降级方案(向后兼容)
- 性能提升 99%,减少 99% 数据传输
- 与 ddns-go hipmdnsmgr.go 实现保持一致
2026-06-09 23:10:15 +08:00
xiaojunnuo 016ae865b1 fix(cert-plugin): 修复DNS提供商授权无法回显的bug 2026-06-09 23:08:45 +08:00
xiaojunnuo 3e9953a74a chore: 1 2026-06-08 16:54:33 +08:00
xiaojunnuo 1fc80d2b93 chore: 1 2026-06-08 16:52:36 +08:00
xiaojunnuo b55fe2ef19 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-06-08 16:48:39 +08:00
xiaojunnuo 71030b7e27 chore: handsfree.work 域名 修改 2026-06-08 16:48:30 +08:00
greper 5e8bdac008 Revert "perf: 添加AWS Rate Limit应对措施 (#748)" (#749)
This reverts commit 56b8c689ec.
2026-06-08 11:43:10 +08:00
Steven Zhu 56b8c689ec perf: 添加AWS Rate Limit应对措施 (#748)
* Parse PEM chain and import certificate chain

Split the PEM in certInfo.crt into a leaf certificate and intermediate chain (using a lookbehind regex), trim the blocks, and pass the chain to ImportCertificateCommand only when present. Replace console.log with this.logger.info and log the returned CertificateArn. This ensures the leaf cert is uploaded separately from its chain and avoids sending an empty CertificateChain.

* Add AWS retry & CloudFront deployment wait

Introduce robust retry and polling helpers to handle AWS throttling and CloudFront propagation. Added AwsClient.withRetry (exponential backoff, handles common throttling errors, default 5 attempts/base 2s) and waitForDistributionDeployed (polls until distribution Status is "Deployed", default 10min timeout/15s interval). Update deploy-to-cloudfront plugin to use withRetry for Get/UpdateDistribution and importCertificate, pass AwsClient into uploadToACM, and wait for each distribution to finish deploying before continuing to avoid PreconditionFailed errors. Improves reliability when facing rate limits and global CloudFront propagation delays; adds informative logging for retry and deployment status.
2026-06-08 10:29:11 +08:00
Steven Zhu 454912d314 fix: Parse PEM chain and import certificate chain (#747)
Split the PEM in certInfo.crt into a leaf certificate and intermediate chain (using a lookbehind regex), trim the blocks, and pass the chain to ImportCertificateCommand only when present. Replace console.log with this.logger.info and log the returned CertificateArn. This ensures the leaf cert is uploaded separately from its chain and avoids sending an empty CertificateChain.
2026-06-08 10:28:39 +08:00
xiaojunnuo 61e3f5761c build: release 2026-06-06 03:06:48 +08:00
xiaojunnuo 2908569841 chore: 1 2026-06-06 03:02:47 +08:00
xiaojunnuo 775226b49f build: publish 2026-06-06 02:38:20 +08:00
xiaojunnuo e3dacb5b3f build: trigger build image 2026-06-06 02:38:08 +08:00
xiaojunnuo cdea411136 v1.41.1 2026-06-06 02:36:50 +08:00
xiaojunnuo fdb000ee7c build: prepare to build 2026-06-06 02:33:06 +08:00
xiaojunnuo 4a0be1c29d build: prepare to build 2026-06-06 02:31:59 +08:00
xiaojunnuo 892d22e225 chore: 1 2026-06-06 02:30:57 +08:00
xiaojunnuo 4958a48b92 build: prepare to build 2026-06-06 02:21:11 +08:00
xiaojunnuo 28bbea85f0 chore: 1 2026-06-06 02:20:09 +08:00
xiaojunnuo 73b3a29cfc build: prepare to build 2026-06-06 02:15:39 +08:00
xiaojunnuo 77b8024453 perf(volcengine-vke): 火山VKE集群证书支持两种类型的证书保密字典 2026-06-06 02:12:47 +08:00
xiaojunnuo 1175e1164b refactor(ui): 统一使用useMounted钩子简化页面初始化逻辑 2026-06-06 00:50:59 +08:00
xiaojunnuo 5546af518e perf: 优化列表页面请求两次的问题 2026-06-05 00:23:08 +08:00
xiaojunnuo 99fd3083f2 perf: 流水线、监控站点支持导出 2026-06-04 23:43:25 +08:00
xiaojunnuo c0df8be832 perf(settings): 新增NO_PROXY代理排除配置 2026-06-04 23:24:29 +08:00
xiaojunnuo 73cab6a6ee chore(auth): 清理已使用的邀请码缓存 2026-06-04 18:28:15 +08:00
xiaojunnuo 7a71e45799 perf: 优化邀请注册流程 2026-06-04 18:22:21 +08:00
xiaojunnuo fdb1d1e6dd build: publish 2026-06-04 12:32:41 +08:00
xiaojunnuo 6dd4d6adeb build: trigger build image 2026-06-04 12:32:29 +08:00
135 changed files with 1355 additions and 463 deletions
+6 -2
View File
@@ -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/
.studio/
# Certd 推广报告,仅本地使用
/popularize/
+79 -1
View File
@@ -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` 等方法**。
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+4 -1
View File
@@ -179,7 +179,10 @@ https://certd.handfree.work/
2. 您的需求我们将优先实现,并且可能将作为专业版功能提供
3. 获得专业版功能
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
> [50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product) https://app.handfree.work/subject/#/app/certd/product
> handfree.work是Certd官方激活码购买平台
专业版、商业版特权对比
+35
View File
@@ -3,6 +3,41 @@
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
* 修复阿里云证书订单orderid 选择出错的问题 ([41254d1](https://github.com/certd/certd/commit/41254d10b748a2d3e6ba43c7e11411650c748d1b))
* **monitor:** 修复开放接口自动创建证书流水线重复触发和等待时间不足的问题 ([91d5c90](https://github.com/certd/certd/commit/91d5c90eb0eaf65c81dddbd2d4d4b404cb8b4d07))
* **pipeline:** 修复批量随机修改定时没有生效的bug ([2e19dda](https://github.com/certd/certd/commit/2e19dda72e70b525a7c269e18e963a5ee602f59f))
### Features
* 商业版支持邀请返佣功能 ([f9a310b](https://github.com/certd/certd/commit/f9a310b6c3bbf30f221482a0c59e9c30080bdfc8))
* 商业版支持邀请推广功能 ([f1d2a10](https://github.com/certd/certd/commit/f1d2a1033a0f8d3dbd91fc9793e07bd0b858b539))
* 新增管理员针对用户流水线和证书监控管理功能 ([0211552](https://github.com/certd/certd/commit/021155278e7375f8487b0531ed1b5ad52512f007))
* 新增套餐激活码功能,通过CDK兑换套餐 ([81d6289](https://github.com/certd/certd/commit/81d6289a8631b073b49f24dee4b14bb1c8f31071))
* 新增推广等级激励功能 ([5096df5](https://github.com/certd/certd/commit/5096df5cc0d8f0ad8aa327b8e2a900ba23714bd8))
* 新增证书申请参数模版管理,开放接口支持使用证书参数模版和指定证书申请参数 ([f8b71a0](https://github.com/certd/certd/commit/f8b71a0e612fad527cf49136335e0b46f0f379cd))
* 支持dns-persist-01持久化验证方式申请证书,优化Acme账号的存储方式 ([67b05e2](https://github.com/certd/certd/commit/67b05e2d75e96b9f855e1ca0b3d0d8d03b92d8e6))
### Performance Improvements
* 插件全局配置支持下拉选项自定义映射功能 ([c637985](https://github.com/certd/certd/commit/c637985575b09196b04cce37ac14fbe68c029bde))
* 商业版提现增加收款二维码上传 ([83a5a21](https://github.com/certd/certd/commit/83a5a21f956e50942541f1532f3a8dcaa5821d34))
* **aliyun-apig:** 优化阿里云API网关部署插件的查询及翻页 ([3e4b7f3](https://github.com/certd/certd/commit/3e4b7f30ac6f3c976c8274bdf256c69b8a2c46db))
* **trade:** 优化商品购买页面的规格展示和折扣计算,支持订单取消 ([6624769](https://github.com/certd/certd/commit/66247690326ce2789900fc9110c08b3502cea655))
## [1.40.5](https://github.com/certd/certd/compare/v1.40.4...v1.40.5) (2026-05-26)
### Bug Fixes
View File
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.41.0"
"version": "1.41.1"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+3 -3
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+1 -1
View File
@@ -263,4 +263,4 @@ export function createChallengeFn(opts = {}) {
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work")
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handfree.work")
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+1 -1
View File
@@ -1 +1 @@
12:22
02:33
+2 -2
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
@@ -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);
});
});
+174 -28
View File
@@ -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<string, string> = {};
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) => {
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+4 -4
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+2 -2
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+2 -2
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+2 -2
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+3 -3
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+7 -7
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
@@ -81,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings {
httpsProxy? = '';
httpProxy? = '';
noProxy? = '';
commonHeaders?: string = '';
reverseProxies?: Record<string, string> = {};
@@ -165,6 +165,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
const opts = {
httpProxy: privateSetting.httpProxy,
httpsProxy: privateSetting.httpsProxy,
noProxy: privateSetting.noProxy,
};
setGlobalProxy(opts);
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
@@ -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
+2 -2
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
@@ -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
+6 -6
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
+6 -6
View File
@@ -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": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4"
"gitHead": "cdea411136fdf56352699a6e278a403e0f53a94f"
}
+2 -2
View File
@@ -4,8 +4,8 @@ VITE_APP_PM_ENABLED=true
VITE_APP_TITLE=Certd
VITE_APP_SLOGAN=让你的证书永不过期
VITE_APP_COPYRIGHT_YEAR=2021-2026
VITE_APP_COPYRIGHT_NAME=handsfree.work
VITE_APP_COPYRIGHT_URL=https://certd.handsfree.work
VITE_APP_COPYRIGHT_NAME=handfree.work
VITE_APP_COPYRIGHT_URL=https://certd.handfree.work
VITE_APP_LOGO=static/images/logo/logo.svg
VITE_APP_LOGIN_LOGO=static/images/logo/rect-black.svg
VITE_APP_PROJECT_PATH=https://github.com/certd/certd
+9
View File
@@ -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
+3 -3
View File
@@ -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",
@@ -14,7 +14,7 @@ export default {
default: undefined,
},
},
emits: ["update:modelValue", "selected-change"],
emits: ["update:modelValue", "selected-change", "change"],
setup(props: any, ctx: any) {
const options = ref<any[]>([]);
@@ -33,7 +33,8 @@ export default {
// if (props.modelValue == null && options.value.length > 0) {
// ctx.emit("update:modelValue", options.value[0].value);
// }
onSelectedChange(props.modelValue);
//selected-changeoption
onSelectedChange(props.modelValue, true);
}
onCreate();
@@ -41,9 +42,12 @@ export default {
ctx.emit("update:modelValue", value);
onSelectedChange(value);
}
function onSelectedChange(value: any) {
function onSelectedChange(value: any, isFirst: boolean = false) {
if (value) {
const option = options.value.find(item => item.value == value);
if (!isFirst) {
ctx.emit("change", value);
}
if (option) {
ctx.emit("selected-change", option);
return;
@@ -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",
@@ -41,6 +41,7 @@ export default {
pi: {
validTime: "Piepline Valid Time",
validTimeHelper: "Not filled in means permanent validity",
permanentValid: "Permanent",
},
types: {
certApply: "Cert Apply",
@@ -4,7 +4,7 @@ export default {
"The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.",
cnameLinkText: "CNAME principle and usage instructions",
cnameDomain: "CNAME Domain",
cnameDomainPlaceholder: "cname.handsfree.work",
cnameDomainPlaceholder: "cname.handfree.work",
cnameDomainHelper:
"Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.",
cnameDomainPattern: "Domain name cannot contain *",
@@ -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",
@@ -86,6 +86,7 @@ export default {
pipelineContent: "流水线内容",
scheduledTaskCount: "定时任务数",
deployTaskCount: "部署任务数",
certDomains: "证书域名",
remainingValidity: "到期剩余",
effectiveTime: "生效时间",
expiryTime: "过期时间",
@@ -41,6 +41,7 @@ export default {
pi: {
validTime: "流水线有效期",
validTimeHelper: "不填则为永久有效",
permanentValid: "永久有效",
},
types: {
certApply: "证书申请",
@@ -3,7 +3,7 @@ export default {
cnameDescription: "此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。",
cnameLinkText: "CNAME功能原理及使用说明",
cnameDomain: "CNAME域名",
cnameDomainPlaceholder: "cname.handsfree.work",
cnameDomainPlaceholder: "cname.handfree.work",
cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名",
cnameDomainPattern: "域名不能使用星号",
cnameProviderSubdomain: "托管子域名",
@@ -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优先",
@@ -107,6 +107,9 @@ function install(app: App, options: any = {}) {
scroll: {
x: 960,
},
rowSelection: {
fixed: "left",
},
size: "small",
pagination: false,
onResizeColumn: (w: number, col: any) => {
@@ -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,
},
},
{
@@ -389,7 +392,7 @@ export const certdResources = [
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
return settingStore.isInviteCommissionEnabled;
},
icon: "ion:gift-outline",
auth: true,
@@ -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,
},
},
@@ -105,6 +105,7 @@ export type InviteSetting = {
export type SysPrivateSetting = {
httpProxy?: string;
httpsProxy?: string;
noProxy?: string;
commonHeaders?: string;
reverseProxies?: any;
dnsResultOrder?: string;
@@ -15,6 +15,7 @@ import { resetAllStores, useAccessStore } from "/@/vben/stores";
import { useUserStore as vbenUserStore } from "/@/vben/stores/modules/user";
import { request } from "/@/api/service";
import { inviteUtils } from "/@/utils/util.invite";
interface UserState {
userInfo: Nullable<UserInfoRes>;
@@ -66,6 +67,9 @@ export const useUserStore = defineStore({
},
async register(user: RegisterReq) {
await UserApi.register(user);
if (user.inviteCode) {
inviteUtils.clear();
}
notification.success({
message: "注册成功,请登录",
});
@@ -85,6 +89,9 @@ export const useUserStore = defineStore({
let loginRes: any = null;
if (loginType === "sms") {
loginRes = await UserApi.loginBySms(params as SmsLoginReq);
if ((params as SmsLoginReq).inviteCode) {
inviteUtils.clear();
}
} else {
loginRes = await UserApi.login(params as LoginReq);
}
@@ -136,12 +143,12 @@ export const useUserStore = defineStore({
} catch (e) {
console.error("注销登录请求失败:", e);
}
// 第三方登录注销
this.oauthLogout();
}
this.resetState();
resetAllStores();
// 第三方登录注销
await this.oauthLogout();
goLogin && router.push("/login");
mitter.emit("app.logout");
},
@@ -0,0 +1,24 @@
import { onActivated, onMounted } from "vue";
/**
*
* - KeepAlive onActivated init
* - onMounted onActivated
*/
export function useMounted(init: () => void | Promise<void>) {
let activated = false;
onActivated(() => {
activated = true;
init();
});
onMounted(() => {
// 让 onActivated 有机会先执行;组件未被 KeepAlive 缓存时 onActivated 不会触发,由这里兜底。
setTimeout(() => {
if (!activated) {
init();
}
});
});
}
@@ -41,6 +41,10 @@ export const inviteUtils = {
}
},
clear() {
localStorage.removeItem(INVITE_STORAGE_KEY);
},
captureFromLocation() {
const hashQuery = window.location.hash?.split("?")[1] || "";
const search = window.location.search?.replace(/^\?/, "") || "";
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api";
@@ -23,14 +24,7 @@ export default defineComponent({
const { t } = useI18n();
const api = createAccessApi("user");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api, permission: { isProjectPermission: true } } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createAddonApi } from "./api";
@@ -26,14 +27,7 @@ export default defineComponent({
createCrudOptions,
context: { api, permission: { isProjectPermission: true } },
});
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
@@ -24,14 +25,7 @@ export default defineComponent({
permission: { isProjectPermission: true },
},
});
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -109,7 +109,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
},
rowHandle: {
fixed: "right",
width: 120,
width: 200,
buttons: {
edit: {
click: ({ row }) => openForm(row),
@@ -11,9 +11,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "CertApplyTemplate",
@@ -25,12 +25,5 @@ const { crudBinding, crudRef, crudExpose } = useFs({
permission: { isProjectPermission: true },
},
});
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -25,7 +25,7 @@ const context: any = {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -62,7 +62,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -62,7 +62,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -14,13 +14,13 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "/src/locales";
import createCrudOptions from "./crud";
import { useCrudPermission } from "/@/plugin/permission";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
const { t } = useI18n();
@@ -52,13 +52,6 @@ const handleBatchDelete = () => {
message.error(t("certd.pleaseSelectRecords"));
}
};
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
<style lang="less"></style>
@@ -300,7 +300,7 @@ async function handleTabChange() {
}
async function refreshInvitePage(autoOpenAgreement = true) {
await settingStore.initOnce();
await settingStore.init();
enabled.value = settingStore.isInviteCommissionEnabled;
loaded.value = true;
if (!enabled.value) {
@@ -11,9 +11,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
const { t } = useI18n();
@@ -22,12 +22,5 @@ defineOptions({
name: "CertStore",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: { isProjectPermission: true } } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -22,8 +22,8 @@
<script lang="ts" setup>
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
@@ -37,12 +37,5 @@ const context: any = {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -27,8 +27,8 @@
<script lang="ts" setup>
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
@@ -42,12 +42,5 @@ const context: any = {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -5,9 +5,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted, ref, Ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "SiteIpCertMonitor",
@@ -21,12 +21,5 @@ const { crudBinding, crudRef, crudExpose } = useFs({
props,
},
});
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createNotificationApi } from "./api";
@@ -23,14 +24,7 @@ export default defineComponent({
const api = createNotificationApi();
notificationProvide(api);
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api, permission: { isProjectPermission: true } } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -11,21 +11,14 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
import { OPEN_API_DOC } from "/@/views/certd/open/openkey/api";
defineOptions({
name: "OpenKey",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: { isProjectPermission: true } } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -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");
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
@@ -24,14 +25,7 @@ export default defineComponent({
permission: { isProjectPermission: true },
},
});
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -129,7 +129,7 @@ const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
@@ -172,9 +172,9 @@ function useStepForm() {
const stepTypeSelected = (item: any) => {
if (item.needPlus && !settingStore.isPlus) {
message.warn("此插件需要开通专业版才能使用");
message.warn("此插件需要开通Certd专业版才能使用");
mitter.emit("openVipModal");
throw new Error("此插件需要开通专业版才能使用");
throw new Error("此插件需要开通Certd专业版才能使用");
}
currentStep.value.type = item.name;
currentStep.value.title = item.title;
@@ -37,7 +37,7 @@ import { useCrudPermission } from "/@/plugin/permission";
const { t } = useI18n();
defineOptions({
name: "CnameRecord",
name: "SubDomain",
});
const context: any = {
permission: {
@@ -68,7 +68,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -14,9 +14,9 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
defineOptions({
name: "PipelineTemplate",
@@ -28,11 +28,5 @@ const { crudBinding, crudRef, crudExpose } = useFs({
},
});
const { t } = useI18n();
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -28,6 +28,7 @@
<script lang="ts" setup>
import { onActivated, onMounted, Ref, ref } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
@@ -117,13 +118,12 @@ onMounted(async () => {
return;
}
await loadProjectDetail();
await crudExpose.doRefresh();
if (migrate === "true") {
openTransferDialog();
}
});
onActivated(async () => {
useMounted(async () => {
await crudExpose.doRefresh();
});
</script>
@@ -53,7 +53,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -15,14 +15,14 @@
</template>
<script lang="ts" setup>
import { computed, onActivated, onMounted, ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import { computed, ref } from "vue";
import createCrudOptions from "./crud";
import { mySuiteApi, SuiteDetail } from "/@/views/certd/suite/mine/api";
import SuiteCard from "/@/views/framework/home/dashboard/suite-card.vue";
import { useMounted } from "/@/use/use-mounted";
defineOptions({
name: "MySuites",
name: "MySuite",
});
const detail = ref<SuiteDetail>({});
const currentSuite = computed(() => {
@@ -39,11 +39,8 @@ async function loadSuiteDetail() {
}
//
onMounted(async () => {
useMounted(async () => {
await loadSuiteDetail();
await crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>
@@ -19,7 +19,7 @@ const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -26,13 +26,14 @@ export async function OauthToken(type: string, validationCode: string) {
});
}
export async function AutoRegister(type: string, code: string) {
export async function AutoRegister(type: string, code: string, inviteCode?: string) {
return await request({
url: apiPrefix + `/autoRegister`,
method: "post",
data: {
validationCode: code,
type,
inviteCode,
},
});
}
@@ -29,6 +29,7 @@ import { useRoute, useRouter } from "vue-router";
import { useUserStore } from "/@/store/user";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/settings";
import { inviteUtils } from "/@/utils/util.invite";
const route = useRoute();
const router = useRouter();
@@ -99,7 +100,11 @@ async function goBindUser() {
async function autoRegister() {
//
const res = await api.AutoRegister(oauthType, bindCode.value);
const inviteCode = inviteUtils.get();
const res = await api.AutoRegister(oauthType, bindCode.value, inviteCode);
if (inviteCode) {
inviteUtils.clear();
}
//
userStore.onLoginSuccess(res);
//
@@ -215,6 +215,8 @@ export default defineComponent({
const handleFinish = async (values: any) => {
try {
//
userStore.resetState();
await userStore.register(
toRaw({
type: registerType.value,
@@ -11,7 +11,8 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue";
import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "../../certd/access/crud";
import { createAccessApi } from "/@/views/certd/access/api";
@@ -21,15 +22,7 @@ export default defineComponent({
setup() {
const api = createAccessApi("sys");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -22,7 +22,7 @@ import { useFs, useUi } from "@fast-crud/fast-crud";
import { useI18n } from "/src/locales";
export default defineComponent({
name: "AuthorityManager",
name: "PermissionManager",
components: { FsPermissionTree },
setup() {
// permissioncommonOptionsactionbarrowHandleshow
@@ -32,7 +32,7 @@ export default defineComponent({
//
onMounted(async () => {
await crudExpose.doRefresh();
// await crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -11,14 +11,15 @@
</template>
<script lang="ts">
import { defineComponent, onActivated, onMounted, ref } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import * as permissionApi from "../permission/api";
import * as api from "./api";
import { message } from "ant-design-vue";
import { defineComponent, ref } from "vue";
import * as permissionApi from "../permission/api";
import FsPermissionTree from "../permission/fs-permission-tree.vue";
import * as api from "./api";
import createCrudOptions from "./crud";
import { UseCrudPermissionCompProps, UseCrudPermissionExtraProps } from "/@/plugin/permission";
import { useMounted } from "/@/use/use-mounted";
import { useI18n } from "/src/locales";
function useAuthz() {
@@ -104,15 +105,7 @@ export default defineComponent({
// ./src/plugin/fast-crud/index.js 75-77
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { authz, permission } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
crudRef,
@@ -8,9 +8,10 @@
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onActivated } from "vue";
import { useCrud, useExpose, useFs } from "@fast-crud/fast-crud";
import { useFs } from "@fast-crud/fast-crud";
import { defineComponent } from "vue";
import createCrudOptions from "./crud";
import { useMounted } from "/@/use/use-mounted";
export default defineComponent({
name: "UserManager",
setup() {
@@ -18,14 +19,7 @@ export default defineComponent({
// commonOptionsactionbarrowHandleshow
// ./src/plugin/fast-crud/index.js 75-77
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: "sys:auth:user" } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
return {
crudBinding,
@@ -32,7 +32,7 @@ import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
name: "CnameProvider",
name: "CnameSetting",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
@@ -56,7 +56,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -56,7 +56,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -62,7 +62,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -53,7 +53,7 @@ const handleBatchDelete = () => {
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -15,7 +15,7 @@
<script lang="ts" setup>
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import createCrudOptions from "./crud";
defineOptions({
@@ -25,11 +25,5 @@ defineOptions({
const context: any = {};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -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<number[]>([]);
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 <span style={{ color: "red" }}></span>;
@@ -14,8 +14,8 @@
</template>
<script lang="ts" setup>
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import createCrudOptions from "./crud";
defineOptions({
@@ -25,11 +25,5 @@ defineOptions({
const context: any = {};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
@@ -21,7 +21,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions
const settingStore = useSettingStore();
//
onMounted(() => {
crudExpose.doRefresh();
// crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
@@ -14,6 +14,11 @@
<div class="helper">{{ t("certd.httpsProxyHelper") }}</div>
</a-form-item>
<a-form-item :label="t('certd.noProxy')" :name="['private', 'noProxy']">
<a-textarea v-model:value="formState.private.noProxy" :placeholder="t('certd.noProxyPlaceholder')" rows="3" />
<div class="helper">{{ t("certd.noProxyHelper") }}</div>
</a-form-item>
<a-form-item :label="t('certd.sys.setting.environmentVars')" :name="['private', 'environmentVars']">
<a-textarea v-model:value="formState.private.environmentVars" :placeholder="environmentVarsExample" rows="4" />
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
@@ -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;
}
}
</style>
@@ -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();
@@ -46,12 +46,13 @@
</template>
<script lang="ts" setup>
import { computed, onActivated, onMounted } from "vue";
import { Modal, notification } from "ant-design-vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud-level";
import { Modal, notification } from "ant-design-vue";
import { computed } from "vue";
import * as api from "./api";
import createCrudOptions from "./crud-level";
import { util } from "/@/utils";
import { useMounted } from "/@/use/use-mounted";
defineOptions({ name: "SysInviteLevel" });
@@ -92,13 +93,7 @@ function confirmRemove(opts: any) {
},
});
}
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>
<style lang="less">
@@ -8,18 +8,12 @@
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud-user-level";
defineOptions({ name: "SysInviteUserLevel" });
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
useMounted(() => crudExpose.doRefresh());
</script>

Some files were not shown because too many files have changed in this diff Show More