Compare commits

..

65 Commits

Author SHA1 Message Date
xiaojunnuo
13ddd7c5f9 v1.33.0 2025-04-12 02:38:31 +08:00
xiaojunnuo
0de015fc8b build: prepare to build 2025-04-12 02:35:34 +08:00
xiaojunnuo
d34fedae01 build: prepare to build 2025-04-12 02:34:03 +08:00
xiaojunnuo
7c623fc467 chore: 新增插件编辑页面跳转
- 在插件创建成功后跳转到编辑页面
- 优化了插件管理功能,提高了用户操作的便捷性
2025-04-12 02:33:44 +08:00
xiaojunnuo
359079c3e6 chore: v21适配多数据库 2025-04-12 02:24:38 +08:00
xiaojunnuo
ba72fa3f05 chore: 2025-04-12 02:10:17 +08:00
xiaojunnuo
23caab5b06 chore: 添加子域名托管解析设置并更新相关提示
- 在证书申请页面添加子域名托管解析设置入口
- 更新域名输入提示,增加子域名托管解析相关说明
- 更改子域名托管解析页面图标
2025-04-12 02:00:40 +08:00
xiaojunnuo
b506bd15a5 chore: 2025-04-12 01:48:08 +08:00
xiaojunnuo
d0d9d68fe6 feat: 支持在线自定义插件,无需源码开发 2025-04-12 01:38:48 +08:00
xiaojunnuo
88134ac130 refactor(plugin): 优化插件配置界面和功能
-调整插件配置界面布局和样式
- 增加插件类型和图标字段
- 修改字段显示逻辑,根据不同插件类型显示相应字段
- 优化插件服务端处理逻辑,支持不同类型的插件配置
2025-04-12 01:34:48 +08:00
xiaojunnuo
3d8a5196a0 refactor(core): 重构访问控制和插件实例化逻辑
- 修改访问控制和插件注册方式,使用异步函数统一实例化逻辑
- 更新相关组件和控制器以适应新的异步实例化方式
- 优化 DNS 提供商选择器,增加访问类型支持
2025-04-12 01:21:50 +08:00
xiaojunnuo
c4fb138ae8 chore: 2025-04-12 00:21:19 +08:00
xiaojunnuo
759cfdaabd pref: 日志中加密授权信息输出替换成星号 2025-04-12 00:14:55 +08:00
xiaojunnuo
3d9620abb0 refactor(plugin): 重构插件定义和安装流程
- 更新插件配置格式,增加依赖库和插件类型字段
- 修改插件安装流程,支持安装依赖插件和第三方库
- 优化插件列表过滤逻辑,按类型筛选插件
- 调整 Dockerfile,使用 Node.js22 镜像并更新 pnpm 安装方式
2025-04-11 23:39:40 +08:00
xiaojunnuo
420b0394a7 Merge remote-tracking branch 'origin/v2-plugin' into v2-plugin 2025-04-11 22:38:16 +08:00
xiaojunnuo
84bb4c8b07 Merge branch 'v2-dev' into v2-plugin 2025-04-11 22:35:35 +08:00
greper
310dbb61ee 发布镜像到 GitHub Packages @5aaee9
发布镜像到 GitHub Packages
2025-04-11 16:53:00 +08:00
Indexyz
9b536af9e6 feat: release image to ghcr 2025-04-11 16:37:31 +08:00
xiaojunnuo
c2ca1ea1e5 chore: 新增插件额外配置功能
- 在插件管理中添加 extra 字段,用于存储额外配置信息
- 实现插件编辑页面的额外配置编辑功能
- 更新数据库结构,增加 extra 列
- 优化代码编辑器的导入方式
- 更新 fast-crud 相关包版本
2025-04-11 14:00:28 +08:00
greper
ada4b226de Lego 支持设定加密算法 @5aaee9
Lego 支持设定加密算法
2025-04-11 12:17:37 +08:00
xiaojunnuo
67f956d4a0 pref: 支持子域名托管的域名证书申请 2025-04-11 12:14:09 +08:00
xiaojunnuo
f68af7dcf2 chore: 2025-04-10 23:44:11 +08:00
xiaojunnuo
be1b6f8edc chore: 2025-04-10 13:30:56 +08:00
xiaojunnuo
1150f62927 Merge branch 'v2-dev' into v2-plugin 2025-04-10 11:48:45 +08:00
xiaojunnuo
b4c7a521b4 chore: 2025-04-10 11:38:51 +08:00
xiaojunnuo
5d083a1536 perf: 增加手动上传证书功能说明 2025-04-10 10:34:10 +08:00
xiaojunnuo
2f5ed3aead fix: 升级mysql驱动,支持mysql8最新版本的认证 2025-04-10 10:24:34 +08:00
xiaojunnuo
2951df0cd9 perf: 隐藏运行策略选项 2025-04-10 09:35:50 +08:00
xiaojunnuo
ec22070957 Merge branch 'v2-dev' into v2-plugin 2025-04-10 00:22:51 +08:00
xiaojunnuo
0e36f03954 chore: plugin default 2025-04-10 00:22:05 +08:00
xiaojunnuo
57309ae3d5 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-09 10:07:14 +08:00
xiaojunnuo
7545194f97 chore: 2025-04-09 00:00:53 +08:00
xiaojunnuo
4bb0918e27 chore: 2025-04-08 23:36:50 +08:00
xiaojunnuo
64e5449ab3 perf: 修复tab页缓存问题 2025-04-08 23:31:25 +08:00
xiaojunnuo
a0eeb17d73 chore: 插件编辑与运行测试beta 2025-04-08 22:56:38 +08:00
xiaojunnuo
c021dd03d3 Merge branch 'v2-dev' into v2-plugin 2025-04-08 21:14:54 +08:00
xiaojunnuo
2f1683b26a fix: 修复eab授权,没有email绑定的bug 2025-04-08 19:54:25 +08:00
xiaojunnuo
c99939f435 chore: 2025-04-08 18:06:12 +08:00
xiaojunnuo
efad8bac3c chore: 2025-04-08 13:53:54 +08:00
xiaojunnuo
eaf68fa463 chore: 2025-04-08 13:40:58 +08:00
xiaojunnuo
9475f2e56c chore: code-editor 2025-04-07 23:52:21 +08:00
xiaojunnuo
2e0c067cd2 chore: 2025-04-07 18:22:39 +08:00
xiaojunnuo
59a6043549 chore: 2025-04-06 23:16:54 +08:00
xiaojunnuo
840a7b7c73 chore: 插件编辑器 2025-04-06 18:06:21 +08:00
xiaojunnuo
61e322678b chore: 2025-04-06 00:20:05 +08:00
xiaojunnuo
04acd08ad2 Merge branch 'v2-dev' into v2-plugin 2025-04-05 19:01:23 +08:00
5aaee9
f3bf4faee0 feat(lego): support set key type 2025-04-05 17:01:41 +08:00
xiaojunnuo
c3603ba220 build: publish 2025-04-05 01:37:41 +08:00
xiaojunnuo
a3a52fd12c build: trigger build image 2025-04-05 01:37:22 +08:00
xiaojunnuo
7c4756da81 v1.32.0 2025-04-05 01:35:54 +08:00
xiaojunnuo
f4fe03c790 build: prepare to build 2025-04-05 01:33:15 +08:00
xiaojunnuo
a748bb9352 build: prepare to build 2025-04-05 01:14:24 +08:00
xiaojunnuo
d24fb6ed48 chore: 2025-04-05 01:13:46 +08:00
xiaojunnuo
021dc5b82c Merge branch 'v2-dev' into v2-plugin 2025-04-05 00:48:23 +08:00
xiaojunnuo
9339b78f80 perf: 又拍云支持云存储 2025-04-05 00:47:34 +08:00
xiaojunnuo
8449f8580d perf: 又拍云支持云存储 2025-04-05 00:46:56 +08:00
xiaojunnuo
0948c5bc69 perf: 优化华为dns解析记录创建和删除问题 2025-04-05 00:24:57 +08:00
xiaojunnuo
857589b365 feat: 优化证书申请速度,修复某些情况下letsencrypt 校验失败的问题 2025-04-04 23:17:05 +08:00
xiaojunnuo
c39b1bf823 fix: 修复从本地dns获取记录报错的bug 2025-04-04 20:46:48 +08:00
xiaojunnuo
545aa50898 Merge branch 'v2-dev' into v2-plugin 2025-04-04 20:14:24 +08:00
xiaojunnuo
298006a4b9 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-03 11:40:56 +08:00
xiaojunnuo
903a4131ab fix: 创建cname记录移除域名两端的空格 2025-04-03 11:39:36 +08:00
xiaojunnuo
a0ec0ddb14 build: publish 2025-04-03 00:33:32 +08:00
xiaojunnuo
46eb876f9b build: trigger build image 2025-04-03 00:33:07 +08:00
xiaojunnuo
071ef281c1 chore: 2025-04-01 22:34:15 +08:00
199 changed files with 4055 additions and 1240 deletions

View File

@@ -10,6 +10,7 @@ on:
# - cron: '17 19 * * *'
permissions:
contents: read
packages: write
jobs:
build-certd-image:
@@ -61,6 +62,13 @@ jobs:
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Login to GitHub Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@@ -76,4 +84,5 @@ jobs:
tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-dev:latest
greper/certd-dev:latest
ghcr.io/${{ github.repository }}:dev-latest

View File

@@ -10,6 +10,7 @@ on:
# - cron: '17 19 * * *'
permissions:
contents: read
packages: write
jobs:
build-certd-image:
@@ -43,7 +44,7 @@ jobs:
# cache: 'npm'
# working-directory: ./packages/ui/certd-client
- run: |
npm install -g pnpm@8.15.7
npm install -g pnpm
pnpm install
npm run build
working-directory: ./packages/ui/certd-client
@@ -61,6 +62,13 @@ jobs:
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Login to GitHub Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@@ -78,7 +86,8 @@ jobs:
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
greper/certd:latest
greper/certd:${{steps.get_certd_version.outputs.result}}
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{steps.get_certd_version.outputs.result}}
# - name: Build armv7
# uses: docker/build-push-action@v6
# with:

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Bug Fixes
* 升级mysql驱动支持mysql8最新版本的认证 ([2f5ed3a](https://github.com/certd/certd/commit/2f5ed3aead97641f2c80d692a50226839016df0b))
* 修复eab授权没有email绑定的bug ([2f1683b](https://github.com/certd/certd/commit/2f1683b26acebbfb7d6e2d751435be04a4e7cab4))
### Features
* 支持在线自定义插件,无需源码开发 ([d0d9d68](https://github.com/certd/certd/commit/d0d9d68fe6740f6ff49fe40b7c9917c5a2e4b442))
* **lego:** support set key type ([f3bf4fa](https://github.com/certd/certd/commit/f3bf4faee0be5bdbfdbcf70a502849ed4c8ed4c4))
* release image to ghcr ([9b536af](https://github.com/certd/certd/commit/9b536af9e656dc89e2a87078c129cad6f591e467))
### Performance Improvements
* 修复tab页缓存问题 ([64e5449](https://github.com/certd/certd/commit/64e5449ab3c6b219b0e89eddad14bfb6b71a0650))
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
* 增加手动上传证书功能说明 ([5d083a1](https://github.com/certd/certd/commit/5d083a153637caddbc6f44e915d9fb2d1ae87b33))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 创建cname记录移除域名两端的空格 ([903a413](https://github.com/certd/certd/commit/903a4131ab5f42c8286cd2150ed1032d486fda2f))
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/certd/certd/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/certd/certd/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/certd/certd/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
* 又拍云支持云存储 ([9339b78](https://github.com/certd/certd/commit/9339b78f801d193472c0af25749e8e7a27ffb7af))
* 又拍云支持云存储 ([8449f85](https://github.com/certd/certd/commit/8449f8580da90c1f6b5d02d07c3236ebaf6cf161))
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Bug Fixes

View File

@@ -85,6 +85,8 @@ https://certd.handfree.work/
* `https://hub.docker.com/r/greper/certd`
* `greper/certd:latest`
* `greper/certd:armv7``greper/certd:[version]-armv7`
* GitHub Packages地址:
* `ghcr.io/certd/certd:latest`
* 镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)

View File

@@ -1 +1 @@
01:49
01:37

View File

@@ -61,6 +61,7 @@ export default defineConfig({
nav: [
{ text: "首页", link: "/" },
{ text: "指南", link: "/guide/" },
{ text: "插件", link: "/deploy/" },
{ text: "商业版", link: "/comm/" },
{ text: "Demo体验", link: "https://certd.handfree.work" }
],
@@ -126,8 +127,9 @@ export default defineConfig({
],
"/deploy/":[
{
text: "部署任务",
text: "部署证书插件",
items: [
{ text: "插件说明", link: "/deploy/index.md" },
{ text: "部署到ESXi", link: "/deploy/ESXi/index.md" },
]
}

4
docs/deploy/index.md Normal file
View File

@@ -0,0 +1,4 @@
# 部署插件说明
## 待完善

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 创建cname记录移除域名两端的空格 ([903a413](https://github.com/certd/certd/commit/903a4131ab5f42c8286cd2150ed1032d486fda2f))
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/certd/certd/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/certd/certd/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/certd/certd/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
* 又拍云支持云存储 ([9339b78](https://github.com/certd/certd/commit/9339b78f801d193472c0af25749e8e7a27ffb7af))
* 又拍云支持云存储 ([8449f85](https://github.com/certd/certd/commit/8449f8580da90c1f6b5d02d07c3236ebaf6cf161))
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Bug Fixes
* 修复ssh支持键盘事件登录 ([8145808](https://github.com/certd/certd/commit/8145808c4370364377b4ffe3ae88ff465b49f20b))
### Performance Improvements
* 支持部署到京东云cdn ([6f17c70](https://github.com/certd/certd/commit/6f17c700b84965baa01b40fe2abaa0a91bcbaffd))
* 支持京东云dns申请证书 ([04d79f9](https://github.com/certd/certd/commit/04d79f9117670be504960b018fd49ae3bf7c1c11))
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
### Performance Improvements

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.31.11"
"version": "1.33.0"
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/publishlab/node-acme-client/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/acme-client
# [1.32.0](https://github.com/publishlab/node-acme-client/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/publishlab/node-acme-client/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/publishlab/node-acme-client/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/publishlab/node-acme-client/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
## [1.31.11](https://github.com/publishlab/node-acme-client/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/acme-client

View File

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

View File

@@ -1,10 +1,10 @@
/**
* ACME auto helper
*/
import { readCsrDomains } from './crypto/index.js';
import { log } from './logger.js';
import { wait } from './wait.js';
import { CancelError } from './error.js';
import { readCsrDomains } from "./crypto/index.js";
import { log } from "./logger.js";
import { wait } from "./wait.js";
import { CancelError } from "./error.js";
const defaultOpts = {
@@ -13,13 +13,13 @@ const defaultOpts = {
preferredChain: null,
termsOfServiceAgreed: false,
skipChallengeVerification: false,
challengePriority: ['http-01', 'dns-01'],
challengePriority: ["http-01", "dns-01"],
challengeCreateFn: async () => {
throw new Error('Missing challengeCreateFn()');
throw new Error("Missing challengeCreateFn()");
},
challengeRemoveFn: async () => {
throw new Error('Missing challengeRemoveFn()');
},
throw new Error("Missing challengeRemoveFn()");
}
};
/**
@@ -30,7 +30,7 @@ const defaultOpts = {
* @returns {Promise<buffer>} Certificate
*/
export default async (client, userOpts) => {
export default async (client, userOpts) => {
const opts = { ...defaultOpts, ...userOpts };
const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
@@ -49,14 +49,13 @@ export default async (client, userOpts) => {
* Register account
*/
log('[auto] Checking account');
log("[auto] Checking account");
try {
client.getAccountUrl();
log('[auto] Account URL already exists, skipping account registration 证书申请账户已存在,跳过注册 ');
}
catch (e) {
log('[auto] Registering account (注册证书申请账户)');
log("[auto] Account URL already exists, skipping account registration 证书申请账户已存在,跳过注册 ");
} catch (e) {
log("[auto] Registering account (注册证书申请账户)");
await client.createAccount(accountPayload);
}
@@ -64,7 +63,7 @@ export default async (client, userOpts) => {
* Parse domains from CSR
*/
log('[auto] Parsing domains from Certificate Signing Request ');
log("[auto] Parsing domains from Certificate Signing Request ");
const { commonName, altNames } = readCsrDomains(opts.csr);
const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d)));
@@ -74,8 +73,8 @@ export default async (client, userOpts) => {
* Place order
*/
log('[auto] Placing new certificate order with ACME provider');
const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: 'dns', value: d })) };
log("[auto] Placing new certificate order with ACME provider");
const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: "dns", value: d })) };
const order = await client.createOrder(orderPayload);
const authorizations = await client.getAuthorizations(order);
@@ -85,82 +84,81 @@ export default async (client, userOpts) => {
* Resolve and satisfy challenges
*/
log('[auto] Resolving and satisfying authorization challenges');
log("[auto] Resolving and satisfying authorization challenges");
const clearTasks = [];
const localVerifyTasks = [];
const completeChallengeTasks = [];
const challengeFunc = async (authz) => {
const d = authz.identifier.value;
let challengeCompleted = false;
/* Skip authz that already has valid status */
if (authz.status === 'valid') {
if (authz.status === "valid") {
log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
return;
}
const keyAuthorizationGetter = async (challenge) => {
return await client.getChallengeKeyAuthorization(challenge);
}
};
try {
log(`[auto] [${d}] Trigger challengeCreateFn()`);
async function deactivateAuth(e) {
log(`[auto] [${d}] Unable to complete challenge: ${e.message}`);
try {
const { recordReq, recordRes, dnsProvider,challenge ,keyAuthorization} = await opts.challengeCreateFn(authz, keyAuthorizationGetter);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider);
}
catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
});
// throw new Error('测试异常');
/* Challenge verification */
if (opts.skipChallengeVerification === true) {
log(`[auto] [${d}] 跳过本地验证skipChallengeVerification=true等待 60s`);
await wait(60 * 1000);
}
else {
log(`[auto] [${d}] 开始本地验证, type = ${challenge.type}`);
try {
await client.verifyChallenge(authz, challenge);
}
catch (e) {
log(`[auto] [${d}] 本地验证失败尝试请求ACME提供商获取状态: ${e.message}`);
}
}
/* Complete challenge and wait for valid status */
log(`[auto] [${d}] 请求ACME提供商完成验证等待返回valid状态`);
await client.completeChallenge(challenge);
challengeCompleted = true;
await client.waitForValidStatus(challenge);
}
catch (e) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
throw e;
log(`[auto] [${d}] Deactivating failed authorization`);
await client.deactivateAuthorization(authz);
} catch (f) {
/* Suppress deactivateAuthorization() errors */
log(`[auto] [${d}] Authorization deactivation threw error: ${f.message}`);
}
}
catch (e) {
/* Deactivate pending authz when unable to complete challenge */
if (!challengeCompleted) {
log(`[auto] [${d}] Unable to complete challenge: ${e.message}`);
log(`[auto] [${d}] Trigger challengeCreateFn()`);
try {
const { recordReq, recordRes, dnsProvider, challenge, keyAuthorization } = await opts.challengeCreateFn(authz, keyAuthorizationGetter);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
log(`[auto] [${d}] Deactivating failed authorization`);
await client.deactivateAuthorization(authz);
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider);
} catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
catch (f) {
/* Suppress deactivateAuthorization() errors */
log(`[auto] [${d}] Authorization deactivation threw error: ${f.message}`);
}
}
});
localVerifyTasks.push(async () => {
/* Challenge verification */
log(`[auto] [${d}] 开始本地验证, type = ${challenge.type}`);
try {
await client.verifyChallenge(authz, challenge);
} catch (e) {
log(`[auto] [${d}] 本地验证失败尝试请求ACME提供商获取状态: ${e.message}`);
}
});
completeChallengeTasks.push(async () => {
/* Complete challenge and wait for valid status */
log(`[auto] [${d}] 请求ACME提供商完成验证`);
try{
await client.completeChallenge(challenge);
}catch (e) {
await deactivateAuth(e);
throw e;
}
challengeCompleted = true;
log(`[auto] [${d}] 等待返回valid状态`);
await client.waitForValidStatus(challenge,d);
});
} catch (e) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
await deactivateAuth(e);
throw e;
}
};
const domainSets = [];
@@ -168,7 +166,7 @@ export default async (client, userOpts) => {
const d = authz.identifier.value;
log(`authorization:domain = ${d}, value = ${JSON.stringify(authz)}`);
if (authz.status === 'valid') {
if (authz.status === "valid") {
log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
return;
}
@@ -192,8 +190,9 @@ export default async (client, userOpts) => {
const allChallengePromises = [];
// eslint-disable-next-line no-restricted-syntax
const challengePromises = [];
allChallengePromises.push(challengePromises);
for (const domainSet of domainSets) {
const challengePromises = [];
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const domain in domainSet) {
const authz = domainSet[domain];
@@ -202,12 +201,11 @@ export default async (client, userOpts) => {
await challengeFunc(authz);
});
}
allChallengePromises.push(challengePromises);
}
log(`[auto] challengeGroups:${allChallengePromises.length}`);
function runAllPromise(tasks) {
async function runAllPromise(tasks) {
let promise = Promise.resolve();
tasks.forEach((task) => {
promise = promise.then(task);
@@ -215,73 +213,60 @@ export default async (client, userOpts) => {
return promise;
}
async function runPromisePa(tasks) {
async function runPromisePa(tasks, waitTime = 5000) {
const results = [];
// eslint-disable-next-line no-await-in-loop,no-restricted-syntax
for (const task of tasks) {
results.push(task());
// eslint-disable-next-line no-await-in-loop
await wait(10000);
await wait(waitTime);
}
return Promise.all(results);
}
try {
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new CancelError('用户取消');
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new CancelError("用户取消");
}
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
if (opts.skipChallengeVerification === true) {
log(`跳过本地验证skipChallengeVerification=true等待 60s`);
await wait(60 * 1000);
} else {
await runPromisePa(localVerifyTasks, 1000);
log("本地校验完成等待30s")
await wait(30 * 1000)
}
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
}
catch (e) {
log(`证书申请失败${e.message}`);
throw e;
}
finally {
if (client.opts.sslProvider !== 'google') {
// letsencrypt 如果同时检出两个TXT记录会以第一个为准就会校验失败所以需要提前删除
// zerossl 此方式测试无问题
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
log(e);
}
}
}
}
}
finally {
if (client.opts.sslProvider === 'google') {
// google 相同的域名txt记录是一样的不能提前删除否则校验失败报错如下
// Error: The TXT record retrieved from _acme-challenge.bbc.handsfree.work.
// at the time the challenge was validated did not contain JshHVu7dt_DT6uYILWhokHefFVad2Q6Mw1L-fNZFcq8
// (the base64url-encoded SHA-256 digest of RlJZNBR0LWnxNK_xd2zqtYVvCiNJOKJ3J1NmCjU_9BjaUJgL3k-qSpIhQ-uF4FBS.NRyqT8fRiq6THzzrvkgzgR5Xai2LsA2SyGLAq_wT3qc).
// See https://tools.ietf.org/html/rfc8555#section-8.4 for more information.
log("开始向提供商请求挑战验证");
await runPromisePa(completeChallengeTasks, 1000);
} catch (e) {
log(`证书申请失败${e.message}`);
throw e;
} finally {
// letsencrypt 如果同时检出两个TXT记录会以第一个为准就会校验失败所以需要提前删除
// zerossl 此方式测试无问题
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
} catch (e) {
log("清理challenge失败");
log(e);
}
}
}
log('challenge结束');
log("challenge结束");
// log('[auto] Waiting for challenge valid status');
// await Promise.all(challengePromises);
@@ -289,7 +274,7 @@ export default async (client, userOpts) => {
* Finalize order and download certificate
*/
log('[auto] Finalizing order and downloading certificate');
log("[auto] Finalizing order and downloading certificate");
const finalized = await client.finalizeOrder(order, opts.csr);
const res = await client.getCertificate(finalized, opts.preferredChain);
return res;

View File

@@ -554,9 +554,9 @@ class AcmeClient {
* ```
*/
async waitForValidStatus(item) {
async waitForValidStatus(item,d) {
if (!item.url) {
throw new Error('Unable to verify status of item, URL not found');
throw new Error(`[${d}] Unable to verify status of item, URL not found`);
}
const verifyFn = async (abort) => {
@@ -568,23 +568,23 @@ class AcmeClient {
const resp = await this.api.apiRequest(item.url, null, [200]);
/* Verify status */
log(`Item has status挑战状态: ${resp.data.status}`);
log(`[${d}] Item has status挑战状态: ${resp.data.status}`);
if (invalidStates.includes(resp.data.status)) {
abort();
throw new Error(util.formatResponseError(resp));
}
else if (pendingStates.includes(resp.data.status)) {
throw new Error('Operation is pending or processing当前仍然在等待状态');
throw new Error(`[${d}] Operation is pending or processing当前仍然在等待状态`);
}
else if (validStates.includes(resp.data.status)) {
return resp.data;
}
throw new Error(`Unexpected item status: ${resp.data.status}`);
throw new Error(`[${d}] Unexpected item status: ${resp.data.status}`);
};
log(`Waiting for valid status 等待valid状态: ${item.url}`, this.backoffOpts);
log(`[${d}] Waiting for valid status 等待valid状态: ${item.url}`, this.backoffOpts);
return util.retry(verifyFn, this.backoffOpts);
}

View File

@@ -98,7 +98,7 @@ export async function walkTxtRecord(recordName,deep = 0) {
try {
/* Default DNS resolver first */
log('从本地DNS服务器获取TXT解析记录');
const res = await walkDnsChallengeRecord(recordName,null,deep);
const res = await walkDnsChallengeRecord(recordName,dns,deep);
if (res && res.length > 0) {
for (const item of res) {
txtRecords.push(item)
@@ -147,12 +147,12 @@ async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '
let recordValues = await walkTxtRecord(recordName);
//去重
recordValues = [...new Set(recordValues)];
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录`);
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录${recordValues}`);
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
throw new Error(`没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`);
}
log(`关键授权匹配成功(${challenge.type}/${recordName},校验成功, ACME challenge verified`);
log(`关键授权匹配成功(${challenge.type}/${recordName}:${keyAuthorization}校验成功 ACME challenge verified`);
return true;
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/basic
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/basic
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
00:28
02:35

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -1,4 +1,4 @@
import log4js, { LoggingEvent, Logger } from 'log4js';
import log4js, { LoggingEvent, Logger } from "log4js";
const OutputAppender = {
configure: (config: any, layouts: any, findAppender: any, levels: any) => {
@@ -21,17 +21,29 @@ const OutputAppender = {
export function resetLogConfigure() {
// @ts-ignore
log4js.configure({
appenders: { std: { type: 'stdout' }, output: { type: OutputAppender } },
categories: { default: { appenders: ['std'], level: 'info' }, pipeline: { appenders: ['std', 'output'], level: 'info' } },
appenders: { std: { type: "stdout" }, output: { type: OutputAppender } },
categories: { default: { appenders: ["std"], level: "info" }, pipeline: { appenders: ["std", "output"], level: "info" } },
});
}
resetLogConfigure();
export const logger = log4js.getLogger('default');
export const logger = log4js.getLogger("default");
export function buildLogger(write: (text: string) => void) {
const logger = log4js.getLogger('pipeline');
logger.addContext('outputHandler', {
write,
const logger = log4js.getLogger("pipeline");
const _secrets: string[] = [];
//@ts-ignore
logger.addSecret = (secret: string) => {
_secrets.push(secret);
};
logger.addContext("outputHandler", {
write: (text: string) => {
for (const item of _secrets) {
//换成同长度的*号, item可能有多行
const reg = new RegExp(item, "g");
text = text.replaceAll(reg, "*".repeat(item.length));
}
write(text);
},
});
return logger;
}

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.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Performance Improvements
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/pipeline
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -16,8 +16,8 @@
"test": "mocha --loader=ts-node/esm"
},
"dependencies": {
"@certd/basic": "^1.31.11",
"@certd/plus-core": "^1.31.11",
"@certd/basic": "^1.33.0",
"@certd/plus-core": "^1.33.0",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -26,7 +26,9 @@ export function IsAccess(define: AccessDefine): ClassDecorator {
target.define = define;
accessRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}
@@ -39,13 +41,15 @@ export function AccessInput(input?: AccessInputDefine): PropertyDecorator {
};
}
export function newAccess(type: string, input: any, ctx?: AccessContext) {
export async function newAccess(type: string, input: any, ctx?: AccessContext) {
const register = accessRegistry.get(type);
if (register == null) {
throw new Error(`access ${type} not found`);
}
// @ts-ignore
const access = new register.target();
const accessCls = await register.target();
// @ts-ignore
const access = new accessCls();
for (const key in input) {
access[key] = input[key];
}
@@ -57,5 +61,6 @@ export function newAccess(type: string, input: any, ctx?: AccessContext) {
};
}
access.setCtx(ctx);
access._type = type;
return access;
}

View File

@@ -1,6 +1,6 @@
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task, ResultError } from "../dt/index.js";
import { RunHistory, RunnableCollection } from "./run-history.js";
import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js";
import { AbstractTaskPlugin, ITaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js";
import { ContextFactory, IContext } from "./context.js";
import { IStorage } from "./storage.js";
import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
@@ -261,6 +261,7 @@ export class Executor {
const resList: ResultType[] = [];
for (const step of task.steps) {
step.runnableType = "step";
// @ts-ignore
const res: ResultType = await this.runWithHistory(step, "step", async () => {
return await this.runStep(step);
});
@@ -276,8 +277,18 @@ export class Executor {
//执行任务
const plugin: RegistryItem<AbstractTaskPlugin> = pluginRegistry.get(step.type);
// @ts-ignore
const instance: ITaskPlugin = new plugin.target();
//@ts-ignore
let instance: ITaskPlugin = null;
try {
//@ts-ignore
const pluginCls = await plugin.target();
//@ts-ignore
instance = new pluginCls();
} catch (e: any) {
currentLogger.error(`实例化插件失败:${e.message}`);
throw new Error(`实例化插件失败`, e);
}
// @ts-ignore
const define: PluginDefine = plugin.define;
const pluginName = define.name;

View File

@@ -150,11 +150,11 @@ export class RunnableCollection {
pipeline.stages = [];
return;
}
pipeline.stages.forEach((stage) => {
pipeline.stages.forEach(stage => {
stage.runnableType = "stage";
stage.tasks.forEach((task) => {
stage.tasks.forEach(task => {
task.runnableType = "task";
task.steps.forEach((step) => {
task.steps.forEach(step => {
step.runnableType = "step";
});
});
@@ -162,7 +162,7 @@ export class RunnableCollection {
}
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
list.forEach((item) => {
list.forEach(item => {
exec(item);
if (item.runnableType === "pipeline") {
// @ts-ignore
@@ -179,7 +179,7 @@ export class RunnableCollection {
public toMap(pipeline: Pipeline) {
const map: RunnableMap = {};
RunnableCollection.each(pipeline.stages, (item) => {
RunnableCollection.each(pipeline.stages, item => {
map[item.id] = item;
});
return map;
@@ -193,7 +193,7 @@ export class RunnableCollection {
if (!this.pipeline) {
return;
}
RunnableCollection.each(this.pipeline.stages, (item) => {
RunnableCollection.each(this.pipeline.stages, item => {
item.status = undefined;
});
}

View File

@@ -26,7 +26,9 @@ export function IsNotification(define: NotificationDefine): ClassDecorator {
target.define = define;
notificationRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}
@@ -44,9 +46,10 @@ export async function newNotification(type: string, input: any, ctx: Notificatio
if (register == null) {
throw new Error(`notification ${type} not found`);
}
// @ts-ignore
const plugin = new register.target();
const pluginCls = await register.target();
// @ts-ignore
const plugin = new pluginCls();
merge(plugin, input);
if (!ctx) {
throw new Error("ctx is required");

View File

@@ -1,7 +1,7 @@
import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store.js";
import { IAccessService } from "../access/index.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
@@ -64,6 +64,9 @@ export type PluginDefine = Registrable & {
};
};
needPlus?: boolean;
showRunStrategy?: boolean;
pluginType?: string; //类型
type?: string; //来源
};
export type ITaskPlugin = {
@@ -155,14 +158,35 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
this.http = ctx.http;
}
async getAccess<T = any>(accessId: string) {
async getAccess<T = any>(accessId: string | number, isCommon = false) {
if (accessId == null) {
throw new Error("您还没有配置授权");
}
const res = await this.ctx.accessService.getById(accessId);
let res: any = null;
if (isCommon) {
res = await this.ctx.accessService.getCommonById(accessId);
} else {
res = await this.ctx.accessService.getById(accessId);
}
if (res == null) {
throw new Error("授权不存在,可能已被删除,请前往任务配置里面重新选择授权");
}
// @ts-ignore
if (this.logger?.addSecret) {
// 隐藏加密信息,不在日志中输出
const type = res._type;
const plugin = accessRegistry.get(type);
const define = plugin.define;
// @ts-ignore
const input = define.input;
for (const key in input) {
if (input[key].encrypt) {
// @ts-ignore
this.logger.addSecret(res[key]);
}
}
}
return res as T;
}

View File

@@ -48,14 +48,26 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
inputMap[item[0]] = item[1];
});
merge(define, { input: inputMap, autowire: autowires, output: outputs });
const defaultConfig = {
showRunStrategy: false,
default: {
strategy: {
runStrategy: 1, // 0:正常执行1:成功后跳过
},
},
};
define = merge(defaultConfig, define, { input: inputMap, autowire: autowires, output: outputs });
Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);
target.define = define;
pluginRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}

View File

@@ -3,14 +3,23 @@ import { AbstractTaskPlugin } from "./api.js";
import { pluginGroups } from "./group.js";
const onRegister = ({ key, value }: OnRegisterContext<AbstractTaskPlugin>) => {
//如果有相同名字的先移除
for (const group of Object.values(pluginGroups)) {
const index = group.plugins.findIndex(plugin => plugin.name === key);
if (index > -1) {
group.plugins.splice(index, 1);
}
}
const group = value?.define?.group as string;
if (group) {
if (pluginGroups.hasOwnProperty(group)) {
// @ts-ignore
pluginGroups[group].plugins.push(value.define);
} else {
pluginGroups.other.plugins.push(value.define);
return;
}
}
pluginGroups.other.plugins.push(value.define);
};
export const pluginRegistry = createRegistry<AbstractTaskPlugin>("plugin", onRegister);

View File

@@ -8,10 +8,10 @@ export type Registrable = {
deprecated?: string;
order?: number;
};
export type TargetGetter<T> = () => Promise<T>;
export type RegistryItem<T> = {
define: Registrable;
target: T;
target: TargetGetter<T>;
};
export type OnRegisterContext<T> = {

View File

@@ -4,5 +4,5 @@ export * from "./config.js";
export * from "./url.js";
export * from "./emit.js";
export type IServiceGetter = {
get: (name: string) => Promise<any>;
get: <T>(name: string) => Promise<T>;
};

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-huawei
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -23,5 +23,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-iframe
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -30,5 +30,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/jdcloud
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/jdcloud
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.31.11",
"version": "1.33.0",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -59,5 +59,6 @@
"Headers",
"fetch"
]
}
},
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-k8s
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-k8s
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -16,7 +16,7 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/basic": "^1.31.11",
"@certd/basic": "^1.33.0",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-server
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-server
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/lib-server

View File

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

View File

@@ -217,4 +217,20 @@ export abstract class BaseService<T> {
}
throw new PermissionException('权限不足');
}
async batchDelete(ids: number[], userId: number) {
if(userId >0){
const list = await this.getRepository().find({
where: {
// @ts-ignore
id: In(ids),
userId,
},
})
// @ts-ignore
ids = list.map(item => item.id)
}
await this.delete(ids);
}
}

View File

@@ -140,7 +140,7 @@ export class AccessService extends BaseService<AccessEntity> {
id: entity.id,
...setting,
};
return newAccess(entity.type, input);
return await newAccess(entity.type, input);
}
async getById(id: any, userId: number): Promise<any> {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.31.11",
"version": "1.33.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Bug Fixes
* 修复eab授权没有email绑定的bug ([2f1683b](https://github.com/certd/certd/commit/2f1683b26acebbfb7d6e2d751435be04a4e7cab4))
### Features
* **lego:** support set key type ([f3bf4fa](https://github.com/certd/certd/commit/f3bf4faee0be5bdbfdbcf70a502849ed4c8ed4c4))
### Performance Improvements
* 增加手动上传证书功能说明 ([5d083a1](https://github.com/certd/certd/commit/5d083a153637caddbc6f44e915d9fb2d1ae87b33))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/plugin-cert
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,10 +15,10 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^1.31.11",
"@certd/basic": "^1.31.11",
"@certd/pipeline": "^1.31.11",
"@certd/plugin-lib": "^1.31.11",
"@certd/acme-client": "^1.33.0",
"@certd/basic": "^1.33.0",
"@certd/pipeline": "^1.33.0",
"@certd/plugin-lib": "^1.33.0",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -41,5 +41,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -33,7 +33,7 @@ export class EabAccess extends BaseAccess {
component: {
placeholder: "绑定一个邮箱",
},
rules: { type: "email", message: "请输入正确的邮箱" },
rules: [{ type: "email", message: "请输入正确的邮箱" }],
helper: "Google的EAB申请证书更换邮箱会导致EAB失效可以在此处绑定一个邮箱避免此问题",
required: true,
})

View File

@@ -27,6 +27,7 @@ export type DnsProviderContext = {
logger: ILogger;
http: HttpClient;
utils: typeof utils;
domainParser: IDomainParser;
};
export interface IDnsProvider<T = any> {
@@ -35,3 +36,11 @@ export interface IDnsProvider<T = any> {
removeRecord(options: RemoveRecordOptions<T>): Promise<void>;
setCtx(ctx: DnsProviderContext): void;
}
export interface ISubDomainsGetter {
getSubDomains(): Promise<string[]>;
}
export interface IDomainParser {
parse(fullDomain: string): Promise<string>;
}

View File

@@ -1,6 +1,4 @@
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, IDnsProvider, RemoveRecordOptions } from "./api.js";
//@ts-ignore
import psl from "psl";
import { dnsProviderRegistry } from "./registry.js";
import { Decorator } from "@certd/pipeline";
import { HttpClient, ILogger } from "@certd/basic";
@@ -16,6 +14,10 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
this.http = ctx.http;
}
async parseDomain(fullDomain: string) {
return await this.ctx.domainParser.parse(fullDomain);
}
abstract createRecord(options: CreateRecordOptions): Promise<T>;
abstract onInstance(): Promise<void>;
@@ -23,14 +25,6 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
abstract removeRecord(options: RemoveRecordOptions<T>): Promise<void>;
}
export function parseDomain(fullDomain: string) {
const parsed = psl.parse(fullDomain) as psl.ParsedDomain;
if (parsed.error) {
throw new Error(`解析${fullDomain}域名失败:` + JSON.stringify(parsed.error));
}
return parsed.domain as string;
}
export async function createDnsProvider(opts: { dnsProviderType: string; context: DnsProviderContext }): Promise<IDnsProvider> {
const { dnsProviderType, context } = opts;
const dnsProviderPlugin = dnsProviderRegistry.get(dnsProviderType);

View File

@@ -0,0 +1,32 @@
import { IDomainParser, ISubDomainsGetter } from "./api";
//@ts-ignore
import psl from "psl";
export class DomainParser implements IDomainParser {
subDomainsGetter: ISubDomainsGetter;
constructor(subDomainsGetter: ISubDomainsGetter) {
this.subDomainsGetter = subDomainsGetter;
}
parseDomain(fullDomain: string) {
const parsed = psl.parse(fullDomain) as psl.ParsedDomain;
if (parsed.error) {
throw new Error(`解析${fullDomain}域名失败:` + JSON.stringify(parsed.error));
}
return parsed.domain as string;
}
async parse(fullDomain: string) {
const subDomains = await this.subDomainsGetter.getSubDomains();
if (subDomains && subDomains.length > 0) {
for (const subDomain of subDomains) {
if (fullDomain.endsWith(subDomain)) {
//找到子域名托管
return subDomain;
}
}
}
return this.parseDomain(fullDomain);
}
}

View File

@@ -5,8 +5,9 @@ import * as _ from "lodash-es";
import { Challenge } from "@certd/acme-client/types/rfc8555";
import { IContext } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic";
import { IDnsProvider, parseDomain } from "../../dns-provider/index.js";
import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js";
import { HttpChallengeUploader } from "./uploads/api.js";
export type CnameVerifyPlan = {
type?: string;
domain: string;
@@ -61,6 +62,8 @@ type AcmeServiceOptions = {
privateKeyType?: PrivateKeyType;
signal?: AbortSignal;
maxCheckRetryCount?: number;
userId: number;
domainParser: IDomainParser;
};
export class AcmeService {
@@ -174,7 +177,7 @@ export class AcmeService {
this.logger.info("Triggered challengeCreateFn()");
const fullDomain = authz.identifier.value;
let domain = parseDomain(fullDomain);
let domain = await this.options.domainParser.parse(fullDomain);
this.logger.info("主域名为:" + domain);
const getChallenge = (type: string) => {
@@ -240,7 +243,7 @@ export class AcmeService {
const cname = cnameVerifyPlan[fullDomain];
if (cname) {
dnsProvider = cname.dnsProvider;
domain = parseDomain(cname.domain);
domain = await this.options.domainParser.parse(cname.domain);
fullRecord = cname.fullRecord;
}
} else {

View File

@@ -27,7 +27,8 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
"1、支持多个域名打到一个证书上例如 foo.com*.foo.com*.bar.com\n" +
"2、子域名被通配符包含的不要填写例如www.foo.com已经被*.foo.com包含不要填写www.foo.com\n" +
"3、泛域名只能通配*号那一级(*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com\n" +
"4、输入一个空格之后再输入下一个",
"4、输入一个空格之后再输入下一个\n" +
"5、如果你配置了子域托管解析请先[设置托管子域名](#/certd/pipeline/subDomain)",
})
domains!: string[];

View File

@@ -9,9 +9,9 @@ export type { CertInfo };
@IsTaskPlugin({
name: "CertApplyUpload",
icon: "ph:certificate",
title: "证书手动上传",
title: "商用证书托管",
group: pluginGroups.cert.key,
desc: "在证书仓库手动上传后触发部署证书",
desc: "手动上传自定义证书后,自动部署(每次证书有更新,都需要手动上传一次)",
default: {
strategy: {
runStrategy: RunStrategy.AlwaysRun,

View File

@@ -4,12 +4,13 @@ import { utils } from "@certd/basic";
import type { CertInfo, CnameVerifyPlan, DomainsVerifyPlan, HttpVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js";
import { AcmeService } from "./acme.js";
import * as _ from "lodash-es";
import { createDnsProvider, DnsProviderContext, IDnsProvider } from "../../dns-provider/index.js";
import { createDnsProvider, DnsProviderContext, IDnsProvider, ISubDomainsGetter } from "../../dns-provider/index.js";
import { CertReader } from "./cert-reader.js";
import { CertApplyBasePlugin } from "./base.js";
import { GoogleClient } from "../../libs/google.js";
import { EabAccess } from "../../access";
import { httpChallengeUploaderFactory } from "./uploads/factory.js";
import { DomainParser } from "../../dns-provider/domain-parser.js";
export * from "./base.js";
export type { CertInfo };
export * from "./cert-reader.js";
@@ -28,6 +29,7 @@ export type DomainVerifyPlanInput = {
domain: string;
type: "cname" | "dns" | "http";
dnsProviderType?: string;
dnsProviderAccessType?: string;
dnsProviderAccessId?: number;
cnameVerifyPlan?: Record<string, CnameRecordInput>;
httpVerifyPlan?: Record<string, HttpRecordInput>;
@@ -98,7 +100,14 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
return {
show: ctx.compute(({form})=>{
return form.challengeType === 'dns'
})
}),
component:{
on:{
selectedChange({form,$event}){
form.dnsProviderAccessType = $event.accessType
}
}
}
}
`,
required: true,
@@ -106,6 +115,8 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
})
dnsProviderType!: string;
dnsProviderAccessType!: string;
@TaskInput({
title: "DNS解析授权",
component: {
@@ -116,7 +127,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
mergeScript: `return {
component:{
type: ctx.compute(({form})=>{
return form.dnsProviderType
return form.dnsProviderAccessType || form.dnsProviderType
})
},
show: ctx.compute(({form})=>{
@@ -287,7 +298,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
if (this.sslProvider === "google") {
if (this.googleAccessId) {
this.logger.info("当前正在使用 google服务账号授权获取EAB");
const googleAccess = await this.ctx.accessService.getById(this.googleAccessId);
const googleAccess = await this.getAccess(this.googleAccessId);
const googleClient = new GoogleClient({
access: googleAccess,
logger: this.logger,
@@ -295,26 +306,29 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
eab = await googleClient.getEab();
} else if (this.eabAccessId) {
this.logger.info("当前正在使用 google EAB授权");
eab = await this.ctx.accessService.getById(this.eabAccessId);
eab = await this.getAccess(this.eabAccessId);
} else if (this.googleCommonEabAccessId) {
this.logger.info("当前正在使用 google公共EAB授权");
eab = await this.ctx.accessService.getCommonById(this.googleCommonEabAccessId);
eab = await this.getAccess(this.googleCommonEabAccessId, true);
} else {
throw new Error("google需要配置EAB授权或服务账号授权");
}
} else if (this.sslProvider === "zerossl") {
if (this.eabAccessId) {
this.logger.info("当前正在使用 zerossl EAB授权");
eab = await this.ctx.accessService.getById(this.eabAccessId);
eab = await this.getAccess(this.eabAccessId);
} else if (this.zerosslCommonEabAccessId) {
this.logger.info("当前正在使用 zerossl 公共EAB授权");
eab = await this.ctx.accessService.getCommonById(this.zerosslCommonEabAccessId);
eab = await this.getAccess(this.zerosslCommonEabAccessId, true);
} else {
throw new Error("zerossl需要配置EAB授权");
}
}
this.eab = eab;
const subDomainsGetter = await this.ctx.serviceGetter.get<ISubDomainsGetter>("subDomainsGetter");
const domainParser = new DomainParser(subDomainsGetter);
this.acme = new AcmeService({
userId: this.ctx.user.id,
userContext: this.userContext,
logger: this.logger,
sslProvider: this.sslProvider,
@@ -325,8 +339,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
privateKeyType: this.privateKeyType,
signal: this.ctx.signal,
maxCheckRetryCount: this.maxCheckRetryCount,
// cnameProxyService: this.ctx.cnameProxyService,
// dnsProviderCreator: this.createDnsProvider.bind(this),
domainParser,
});
}
@@ -356,7 +369,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
domainsVerifyPlan = await this.createDomainsVerifyPlan();
} else {
const dnsProviderType = this.dnsProviderType;
const access = await this.ctx.accessService.getById(this.dnsProviderAccess);
const access = await this.getAccess(this.dnsProviderAccess);
dnsProvider = await this.createDnsProvider(dnsProviderType, access);
}
@@ -387,7 +400,8 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
}
async createDnsProvider(dnsProviderType: string, dnsProviderAccess: any): Promise<IDnsProvider> {
const context: DnsProviderContext = { access: dnsProviderAccess, logger: this.logger, http: this.ctx.http, utils };
const domainParser = this.acme.options.domainParser;
const context: DnsProviderContext = { access: dnsProviderAccess, logger: this.logger, http: this.ctx.http, utils, domainParser };
return await createDnsProvider({
dnsProviderType,
context,
@@ -402,7 +416,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
const cnameVerifyPlan: Record<string, CnameVerifyPlan> = {};
const httpVerifyPlan: Record<string, HttpVerifyPlan> = {};
if (domainVerifyPlan.type === "dns") {
const access = await this.ctx.accessService.getById(domainVerifyPlan.dnsProviderAccessId);
const access = await this.getAccess(domainVerifyPlan.dnsProviderAccessId);
dnsProvider = await this.createDnsProvider(domainVerifyPlan.dnsProviderType, access);
} else if (domainVerifyPlan.type === "cname") {
for (const key in domainVerifyPlan.cnameVerifyPlan) {
@@ -426,7 +440,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
};
for (const key in domainVerifyPlan.httpVerifyPlan) {
const httpRecord = domainVerifyPlan.httpVerifyPlan[key];
const access = await this.ctx.accessService.getById(httpRecord.httpUploaderAccess);
const access = await this.getAccess(httpRecord.httpUploaderAccess);
let rootDir = httpRecord.httpUploadRootDir;
if (!rootDir.endsWith("/") && !rootDir.endsWith("\\")) {
rootDir = rootDir + "/";

View File

@@ -9,6 +9,7 @@ import JSZip from "jszip";
export { CertReader };
export type { CertInfo };
export type PrivateKeyType = "rsa2048" | "rsa3072" | "rsa4096" | "rsa8192" | "ec256" | "ec384";
@IsTaskPlugin({
name: "CertApplyLego",
@@ -90,6 +91,28 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
})
customArgs = "";
@TaskInput({
title: "加密算法",
value: "ec256",
component: {
name: "a-select",
vModel: "value",
options: [
{ value: "rsa2048", label: "RSA 2048" },
{ value: "rsa3072", label: "RSA 3072" },
{ value: "rsa4096", label: "RSA 4096" },
{ value: "rsa8192", label: "RSA 8192" },
{ value: "ec256", label: "EC 256" },
{ value: "ec384", label: "EC 384" },
// { value: "ec_521", label: "EC 521" },
],
},
helper: "如无特殊需求,默认即可",
required: true,
})
privateKeyType!: PrivateKeyType;
eab?: EabAccess;
async onInstance() {
@@ -120,7 +143,7 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
if (this.eab) {
eabArgs = ` --eab --kid "${this.eab.kid}" --hmac "${this.eab.hmacKey}"`;
}
const keyType = "-k rsa2048";
const keyType = `-k ${this.privateKeyType}`;
const saveDir = `./data/.lego/pipeline_${this.pipeline.id}/`;
const savePathArgs = `--path "${saveDir}"`;

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/plugin-lib
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/plugin-lib
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.31.11",
"version": "1.33.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,8 +16,8 @@
},
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/basic": "^1.31.11",
"@certd/pipeline": "^1.31.11",
"@certd/basic": "^1.33.0",
"@certd/pipeline": "^1.33.0",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.21.0",
"basic-ftp": "^5.0.5",
@@ -48,5 +48,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "2e30fff221c19e46e098b0dc3250a6b7c5e6a5e0"
"gitHead": "7c4756da815758b79eb7b7dae0be02d7a8435e0a"
}

View File

@@ -139,6 +139,7 @@ export class AsyncSsh2Client {
script: string,
opts: {
throwOnStdErr?: boolean;
env?: any;
} = {}
): Promise<string> {
if (!script) {
@@ -153,7 +154,7 @@ export class AsyncSsh2Client {
// }
return new Promise((resolve, reject) => {
this.logger.info(`执行命令:[${this.connConf.host}][exec]: \n` + script);
this.conn.exec(script, (err: Error, stream: any) => {
this.conn.exec(script, { pty: true, env: opts.env }, (err: Error, stream: any) => {
if (err) {
reject(err);
return;
@@ -456,7 +457,7 @@ export class SshClient {
script = envScripts.join(newLine) + newLine + script;
}
}
return await conn.exec(script as string);
return await conn.exec(script as string, { env: options.env });
},
});
}

View File

@@ -1,16 +1,16 @@
FROM node:20-alpine AS builder
FROM node:22-alpine AS builder
WORKDIR /workspace/
COPY . /workspace/
# armv7 目前只能用node18 pnpm9不支持node18,所以pnpm只能用8.15.7版本
# https://github.com/nodejs/docker-node/issues/1946
RUN npm install -g pnpm@8.15.7
RUN npm install -g pnpm
#RUN cd /workspace/certd-client && pnpm install && npm run build
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
FROM node:20-alpine
FROM node:22-alpine
EXPOSE 7001
EXPOSE 7002
RUN apk add --no-cache openssl

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Performance Improvements
* 修复tab页缓存问题 ([64e5449](https://github.com/certd/certd/commit/64e5449ab3c6b219b0e89eddad14bfb6b71a0650))
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
* 增加手动上传证书功能说明 ([5d083a1](https://github.com/certd/certd/commit/5d083a153637caddbc6f44e915d9fb2d1ae87b33))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/certd/certd/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.31.11",
"version": "1.33.0",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -29,10 +29,10 @@
"@aws-sdk/client-s3": "^3.535.0",
"@aws-sdk/s3-request-presigner": "^3.535.0",
"@ctrl/tinycolor": "^4.1.0",
"@fast-crud/fast-crud": "^1.25.4",
"@fast-crud/fast-extends": "^1.25.4",
"@fast-crud/ui-antdv4": "^1.25.4",
"@fast-crud/ui-interface": "^1.25.4",
"@fast-crud/fast-crud": "^1.25.8",
"@fast-crud/fast-extends": "^1.25.8",
"@fast-crud/ui-antdv4": "^1.25.8",
"@fast-crud/ui-interface": "^1.25.8",
"@iconify/tailwind": "^1.2.0",
"@iconify/vue": "^4.1.1",
"@manypkg/get-packages": "^2.2.2",
@@ -63,9 +63,12 @@
"echarts": "^5.5.1",
"highlight.js": "^11.9.0",
"humanize-duration": "^3.27.3",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.477.0",
"mitt": "^3.0.1",
"monaco-editor": "^0.52.2",
"monaco-yaml": "^5.3.1",
"nanoid": "^4.0.0",
"node-forge": "^1.3.1",
"nprogress": "^0.2.0",
@@ -96,8 +99,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.31.11",
"@certd/pipeline": "^1.31.11",
"@certd/lib-iframe": "^1.33.0",
"@certd/pipeline": "^1.33.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -1,9 +1,9 @@
<template>
<AConfigProvider :locale="locale" :theme="tokenTheme">
<fs-form-provider>
<FsFormProvider>
<contextHolder />
<router-view />
</fs-form-provider>
</FsFormProvider>
</AConfigProvider>
</template>

View File

@@ -0,0 +1,37 @@
export async function importJsYaml() {
return await import("js-yaml");
}
export async function importYamlContribution() {
await import("monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution");
}
export async function importJsonContribution() {
await import("monaco-editor/esm/vs/language/json/monaco.contribution");
}
export async function importJavascriptContribution() {
await import("monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution");
}
export async function importMonacoYaml() {
return await import("monaco-yaml");
}
export async function importWorks() {
const editorWorker = await import("monaco-editor/esm/vs/editor/editor.worker?worker");
const jsonWorker = await import("monaco-editor/esm/vs/language/json/json.worker?worker");
const cssWorker = await import("monaco-editor/esm/vs/language/css/css.worker?worker");
const htmlWorker = await import("monaco-editor/esm/vs/language/html/html.worker?worker");
const tsWorker = await import("monaco-editor/esm/vs/language/typescript/ts.worker?worker");
const yamlWorker = await import("./yaml.worker?worker");
return {
editorWorker,
jsonWorker,
cssWorker,
htmlWorker,
tsWorker,
yamlWorker,
};
}

View File

@@ -0,0 +1,40 @@
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import yamlWorker from "./yaml.worker?worker";
const WorkerBucket: any = {};
/**
* 注册自定义worker
* @param name
* @param worker
*/
export function registerWorker(name: string, worker: any) {
WorkerBucket[name] = worker;
}
//@ts-ignore
window.MonacoEnvironment = {
//@ts-ignore
getWorker(_, label) {
const custom = WorkerBucket[label];
if (custom) {
return new custom();
}
if (label === "json") {
return new jsonWorker();
} else if (label === "css" || label === "scss" || label === "less") {
return new cssWorker();
} else if (label === "html" || label === "handlebars" || label === "razor") {
return new htmlWorker();
} else if (label === "typescript" || label === "javascript") {
return new tsWorker();
} else if (label === "yaml" || label === "yml") {
//@ts-ignore
return new yamlWorker();
}
return new editorWorker();
},
};

View File

@@ -0,0 +1,261 @@
<template>
<div ref="monacoRef" class="fs-editor-code"></div>
</template>
<script lang="ts" setup>
import * as monaco from "monaco-editor";
// import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { cloneDeep, debounce as lodashDebounce } from "lodash-es";
import { initWorkers } from "./workers";
import { importJavascriptContribution, importJsonContribution, importMonacoYaml, importYamlContribution } from "./async-import";
/**
* config:
* value: '', // 编辑器初始文本
* language: 'javascript', // 语言
* theme: 'vs', // 主题
* readOnly: false, // 是否只读
* minimap: { enabled: false }, // 是否启用小地图
* fontSize: 14, // 字体大小
* tabSize: 2, // tab缩进长度
* automaticLayout: true, // 自动布局
* lineNumbers: 'off', // 是否启用行号
* contextmenu: true, // 是否启用上下文菜单
* folding: true, // 是否启用代码折叠
* foldingStrategy: 'auto', // 代码折叠策略
* wordWrap: 'on', // 自动换行设置
* wrappingIndent: 'indent', // 换行缩进
* formatOnPaste: true, // 粘贴时是否自动格式化
* formatOnType: true, // 输入时是否自动格式化
* dragAndDrop: true, // 是否允许拖放
* cursorStyle: 'line', // 光标样式
* cursorBlinking: 'blink', // 光标闪烁方式
* scrollbar: {
* vertical: 'auto', // 垂直滚动条的显示方式
* horizontal: 'auto', // 水平滚动条的显示方式
* verticalScrollbarSize: 2, // 垂直滚动条的宽
* horizontalScrollbarSize: 2, // 水平滚动条的高度
* }
*/
const props = defineProps<{
language?: string;
modelValue?: string;
config?: any;
schema?: any;
debounce?: number;
init?: any;
readonly?: boolean;
disabled?: boolean;
id?: string;
}>();
export type EditorCodeCtx = {
// monaco对象
monaco: any;
//语言
language: string;
//配置
config: any;
//editor实例对象
instance?: any;
schema?: any;
};
const monacoRef = ref();
let instanceRef: any = null;
function disposeEditor() {
if (instanceRef) {
instanceRef.dispose(); //使用完成销毁实例
}
}
onUnmounted(() => {
disposeEditor();
});
const emits = defineEmits(["update:modelValue", "change", "ready", "save"]);
const emitValue = lodashDebounce((value: any) => {
emits("update:modelValue", value);
}, props.debounce || 500);
async function createEditor(ctx: EditorCodeCtx) {
disposeEditor();
const instance = monaco.editor.create(monacoRef.value, {
automaticLayout: true,
value: props.modelValue,
language: ctx.language,
theme: "vs-dark",
minimap: { enabled: false },
insertSpaces: true,
tabSize: 2,
autoIndent: true, // 自动布局
renderWhitespace: true,
readOnly: props.readonly || props.disabled,
hover: {
enabled: true,
},
...ctx.config,
});
// @event `change`
instance.onDidChangeModelContent(event => {
const value = instance.getValue();
if (props.modelValue !== value) {
emits("change", value);
emitValue(value);
}
});
instanceRef = instance;
ctx.instance = instance;
emits("ready", ctx);
if (props.modelValue) {
instanceRef.setValue(props.modelValue);
}
instance.addAction({
id: "custom_save",
label: "save",
contextMenuOrder: 1, // 菜单项的排序权重 越小,越靠前
contextMenuGroupId: "customCommand", // 菜单项的分组
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
run: async () => {
await instanceRef.getAction("editor.action.formatDocument").run();
emits("save", {
value: props.modelValue,
});
}, //方法
});
return instance;
}
async function initJavascript(ctx: EditorCodeCtx) {
await importJavascriptContribution();
monaco.languages.register({ id: "javascript" });
}
async function initJson(ctx: EditorCodeCtx) {
await importJsonContribution();
monaco.languages.register({ id: "json" });
const schemas = [];
if (ctx.schema) {
schemas.push({
uri: "http://myserver/foo-schema.json", // id of the first schema
fileMatch: ["*"], // associate with our model
schema: {
...ctx.schema,
},
});
}
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
enableSchemaRequest: false,
schemas,
});
}
async function initYaml(ctx: EditorCodeCtx) {
await importYamlContribution();
const { configureMonacoYaml } = await importMonacoYaml();
monaco.languages.register({ id: "yaml" });
const id = `fs-editor-code-${props.id || ""}.yaml`;
const uri = monaco.Uri.parse(id);
const schemas = [];
if (ctx.schema) {
schemas.push({
fileMatch: ["*"], // associate with our model
schema: {
...ctx.schema,
},
uri: id,
});
}
configureMonacoYaml(monaco, {
schemas,
format: true,
hover: true,
completion: true,
validate: true,
isKubernetes: false,
enableSchemaRequest: false,
});
const oldModel = monaco.editor.getModel(uri);
if (oldModel) {
oldModel.dispose();
}
ctx.config.model = monaco.editor.createModel(props.modelValue, "yaml", uri);
}
async function doInit() {
await initWorkers();
const ctx: EditorCodeCtx = {
monaco,
language: props.language || "javascript",
config: cloneDeep(props.config || {}),
schema: props.schema,
};
if (ctx.language === "javascript") {
await initJavascript(ctx);
} else if (ctx.language === "yaml") {
await initYaml(ctx);
} else if (ctx.language === "json") {
await initJson(ctx);
}
await createEditor(ctx);
}
// watch(
// () => {
// return {
// language: props.language,
// config: props.config,
// };
// },
// (value: any) => {
// doInit();
// }
// );
watch(
() => {
return props.modelValue;
},
newValue => {
if (instanceRef) {
const editor = instanceRef;
if (newValue !== editor.getValue()) {
editor.setValue(newValue);
}
}
},
{
immediate: true,
}
);
onMounted(async () => {
await doInit();
});
</script>
<style lang="less">
.fs-editor-code {
min-height: 100px;
width: 100%;
border: 1px solid #eee;
border-radius: 5px;
position: relative;
}
.monaco-editor .hover-content {
z-index: 1000 !important;
}
</style>

View File

@@ -0,0 +1,37 @@
import { importJsYaml } from "./async-import";
const jsonRule = {
validator: async (rule: any, value: any) => {
//校验value json的有效性
if (value) {
try {
JSON.parse(value);
} catch (e: any) {
console.error(e);
throw new Error("json格式错误:" + e.message);
}
}
},
message: "json格式错误",
};
const yamlRule = {
validator: async (rule: any, value: any) => {
//校验value yaml的有效性
if (value) {
try {
const yaml = await importJsYaml();
yaml.load(value, { schema: yaml.JSON_SCHEMA });
} catch (e: any) {
console.error(e);
throw new Error("yaml格式错误:" + e.message);
}
}
},
message: "yaml格式错误",
};
export const FsEditorCodeValidators = {
jsonRule,
yamlRule,
};

View File

@@ -0,0 +1,56 @@
const WorkerBucket: any = {};
/**
* 注册自定义worker
* @param name
* @param worker
*/
export function registerWorker(name: string, worker: any) {
WorkerBucket[name] = worker;
}
export async function initWorkers() {
if (window.MonacoEnvironment) {
return;
}
// const { editorWorker, jsonWorker, cssWorker, htmlWorker, tsWorker } = await importWorks();
//
// // const editorWorker = new Worker(new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url));
// // const jsonWorker = new Worker(new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url));
// // const cssWorker = new Worker(new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url));
// // const htmlWorker = new Worker(new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url));
// // const tsWorker = new Worker(new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url));
// // const yamlWorker = new Worker(new URL("./yaml.worker.js", import.meta.url));
const editorWorker = await import("monaco-editor/esm/vs/editor/editor.worker?worker");
const jsonWorker = await import("monaco-editor/esm/vs/language/json/json.worker?worker");
const cssWorker = await import("monaco-editor/esm/vs/language/css/css.worker?worker");
const htmlWorker = await import("monaco-editor/esm/vs/language/html/html.worker?worker");
const tsWorker = await import("monaco-editor/esm/vs/language/typescript/ts.worker?worker");
const yamlWorker = await import("./yaml.worker?worker");
//@ts-ignore
window.MonacoEnvironment = {
//@ts-ignore
getWorker(_, label) {
const custom = WorkerBucket[label];
if (custom) {
return new custom();
}
if (label === "json") {
return new jsonWorker.default();
} else if (label === "css" || label === "scss" || label === "less") {
return new cssWorker.default();
} else if (label === "html" || label === "handlebars" || label === "razor") {
return new htmlWorker.default();
} else if (label === "typescript" || label === "javascript") {
return new tsWorker.default();
} else if (label === "yaml" || label === "yml") {
//@ts-ignore
return new yamlWorker.default();
}
return new editorWorker.default();
},
};
}

View File

@@ -0,0 +1 @@
export * from "monaco-yaml/yaml.worker.js";

View File

@@ -12,12 +12,18 @@ import IconSelect from "./icon-select.vue";
import ExpiresTimeText from "./expires-time-text.vue";
import FileInput from "./file-input.vue";
import PemInput from "./pem-input.vue";
import { defineAsyncComponent } from "vue";
export default {
install(app: any) {
app.component(
"CodeEditor",
defineAsyncComponent(() => import("./code-editor/index.vue"))
);
app.component("PiContainer", PiContainer);
app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput);
app.component("PemInput", PemInput);
// app.component("CodeEditor", CodeEditor);
app.component("CronLight", CronLight);
app.component("CronEditor", CronEditor);

View File

@@ -5,6 +5,6 @@ const apiPrefix = "/pi/dnsProvider";
export async function GetList() {
return await request({
url: apiPrefix + "/list",
method: "post"
method: "post",
});
}

View File

@@ -11,10 +11,10 @@ export default {
props: {
modelValue: {
type: String,
default: undefined
}
default: undefined,
},
},
emits: ["update:modelValue"],
emits: ["update:modelValue", "selected-change"],
setup(props: any, ctx: any) {
const options = ref<any[]>([]);
@@ -25,24 +25,36 @@ export default {
array.push({
value: item.name,
label: item.title,
icon: item.icon
icon: item.icon,
accessType: item.accessType,
});
}
options.value = array;
// if (props.modelValue == null && options.value.length > 0) {
// ctx.emit("update:modelValue", options.value[0].value);
// }
onSelectedChange(props.modelValue);
}
onCreate();
function onChanged(value: any) {
ctx.emit("update:modelValue", value);
onSelectedChange(value);
}
function onSelectedChange(value: any) {
if (value) {
const option = options.value.find(item => item.value == value);
if (option) {
ctx.emit("selected-change", option);
return;
}
}
}
return {
options,
onChanged
onChanged,
};
}
},
};
</script>

View File

@@ -1,6 +1,7 @@
import { request } from "/src/api/service";
const apiPrefix = "/cname/record";
const subDomainApiPrefix = "/pi/subDomain";
export type CnameRecord = {
id?: number;
@@ -18,7 +19,7 @@ export type DomainGroupItem = {
export async function GetList() {
return await request({
url: apiPrefix + "/list",
method: "post"
method: "post",
});
}
@@ -28,8 +29,8 @@ export async function GetByDomain(domain: string) {
method: "post",
data: {
domain,
createOnNotFound: true
}
createOnNotFound: true,
},
});
}
@@ -38,7 +39,17 @@ export async function DoVerify(id: number) {
url: apiPrefix + "/verify",
method: "post",
data: {
id
}
id,
},
});
}
export async function ParseDomain(fullDomain: string) {
return await request({
url: subDomainApiPrefix + "/parseDomain",
method: "post",
data: {
fullDomain,
},
});
}

View File

@@ -35,7 +35,7 @@ import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-edito
import { dict } from "@fast-crud/fast-crud";
defineOptions({
name: "HttpVerifyPlan"
name: "HttpVerifyPlan",
});
const emit = defineEmits(["update:modelValue", "change"]);
@@ -53,12 +53,12 @@ watch(
(value: any) => {
if (value) {
records.value = {
...value
...value,
};
}
},
{
immediate: true
immediate: true,
}
);
@@ -75,8 +75,8 @@ const uploaderTypeDict = dict({
{ label: "阿里云OSS", value: "alioss" },
{ label: "腾讯云COS", value: "tencentcos" },
{ label: "七牛OSS", value: "qiniuoss" },
{ label: "SSH(已废弃请选择SFTP方式)", value: "ssh", disabled: true }
]
{ label: "SSH(已废弃请选择SFTP方式)", value: "ssh", disabled: true },
],
});
</script>

View File

@@ -38,6 +38,7 @@
:dict="dnsProviderTypeDict"
placeholder="DNS提供商"
@change="onPlanChanged"
@selected-change="onDnsProviderChange(item, $event)"
></fs-dict-select>
</span>
</div>
@@ -45,13 +46,7 @@
<div class="form-item">
<span class="label">DNS授权</span>
<span class="input">
<access-selector
v-model="item.dnsProviderAccessId"
size="small"
:type="item.dnsProviderType"
placeholder="请选择"
@change="onPlanChanged"
></access-selector>
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" placeholder="请选择" @change="onPlanChanged"></access-selector>
</span>
</div>
</div>
@@ -80,28 +75,27 @@ import { dict, FsDictSelect } from "@fast-crud/fast-crud";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import CnameVerifyPlan from "./cname-verify-plan.vue";
import HttpVerifyPlan from "./http-verify-plan.vue";
//@ts-ignore
import psl from "psl";
import { Form } from "ant-design-vue";
import { DomainsVerifyPlanInput } from "./type";
import { CnameRecord, DomainGroupItem } from "./api";
import { DomainGroupItem, ParseDomain } from "./api";
defineOptions({
name: "DomainsVerifyPlanEditor"
name: "DomainsVerifyPlanEditor",
});
const challengeTypeOptions = ref<any[]>([
{
label: "DNS验证",
value: "dns"
value: "dns",
},
{
label: "CNAME验证",
value: "cname"
value: "cname",
},
{
label: "HTTP验证",
value: "http"
}
value: "http",
},
]);
const props = defineProps<{
@@ -114,6 +108,10 @@ const emit = defineEmits<{
"update:modelValue": any;
}>();
function onDnsProviderChange(item: any, option: any) {
item.dnsProviderAccessType = option.accessType;
}
const fullscreen = ref(false);
function fullscreenExit() {
if (fullscreen.value) {
@@ -122,7 +120,7 @@ function fullscreenExit() {
}
const planRef = ref<DomainsVerifyPlanInput>(props.modelValue || {});
const dnsProviderTypeDict = dict({
url: "pi/dnsProvider/dnsProviderTypeDict"
url: "pi/dnsProvider/dnsProviderTypeDict",
});
const formItemContext = Form.useInjectFormItemContext();
@@ -139,7 +137,7 @@ function showError(error: string) {
type DomainGroup = Record<string, DomainGroupItem>;
function onDomainsChanged(domains: string[]) {
async function onDomainsChanged(domains: string[]) {
if (domains == null) {
return;
}
@@ -147,12 +145,7 @@ function onDomainsChanged(domains: string[]) {
const domainGroups: DomainGroup = {};
for (let domain of domains) {
const keyDomain = domain.replace("*.", "");
const parsed = psl.parse(keyDomain);
if (parsed.error) {
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
continue;
}
const mainDomain = parsed.domain;
const mainDomain = await ParseDomain(keyDomain);
if (mainDomain == null) {
continue;
}
@@ -161,7 +154,7 @@ function onDomainsChanged(domains: string[]) {
group = {
domain: mainDomain,
domains: [],
keySubDomains: []
keySubDomains: [],
} as DomainGroupItem;
domainGroups[mainDomain] = group;
}
@@ -180,7 +173,7 @@ function onDomainsChanged(domains: string[]) {
//@ts-ignore
cnameVerifyPlan: {},
//@ts-ignore
httpVerifyPlan: {}
httpVerifyPlan: {},
};
planRef.value[domain] = planItem;
}
@@ -196,7 +189,7 @@ function onDomainsChanged(domains: string[]) {
if (!cnameOrigin[subDomain]) {
//@ts-ignore
planItem.cnameVerifyPlan[subDomain] = {
id: 0
id: 0,
};
} else {
planItem.cnameVerifyPlan[subDomain] = cnameOrigin[subDomain];
@@ -205,14 +198,14 @@ function onDomainsChanged(domains: string[]) {
if (!cnamePlan[subDomain]) {
//@ts-ignore
cnamePlan[subDomain] = {
id: 0
id: 0,
};
}
if (!httpOrigin[subDomain]) {
//@ts-ignore
planItem.httpVerifyPlan[subDomain] = {
domain: subDomain
domain: subDomain,
};
} else {
planItem.httpVerifyPlan[subDomain] = httpOrigin[subDomain];
@@ -221,7 +214,7 @@ function onDomainsChanged(domains: string[]) {
if (!httpPlan[subDomain]) {
//@ts-ignore
httpPlan[subDomain] = {
domain: subDomain
domain: subDomain,
};
}
}
@@ -255,7 +248,7 @@ watch(
},
{
immediate: true,
deep: true
deep: true,
}
);
</script>

View File

@@ -12,6 +12,7 @@ export type DomainVerifyPlanInput = {
domains: string[];
type: "cname" | "dns" | "http";
dnsProviderType?: string;
dnsProviderAccessType?: string;
dnsProviderAccessId?: number;
cnameVerifyPlan?: Record<string, CnameRecord>;
httpVerifyPlan?: Record<string, HttpRecord>;

View File

@@ -13,6 +13,7 @@ import plugin from "./plugin/";
import { setupVben } from "./vben";
import { util } from "/@/utils";
import { initPreferences } from "/@/vben/preferences";
// import "./components/code-editor/import-works";
// @ts-ignore
async function bootstrap() {
const app = createApp(App);
@@ -33,9 +34,9 @@ async function bootstrap() {
namespace,
overrides: {
app: {
name: import.meta.env.VITE_APP_TITLE
}
}
name: import.meta.env.VITE_APP_TITLE,
},
},
});
app.mount("#app");

View File

@@ -222,5 +222,5 @@ export default {
"AResult",
defineAsyncComponent(() => import("ant-design-vue/es/result"))
);
}
},
};

View File

@@ -2,5 +2,5 @@ import antdv from "ant-design-vue";
export default {
install(app: any) {
app.use(antdv);
}
},
};

View File

@@ -22,7 +22,7 @@ function buildAccessedMenus(menus: any) {
}
}
const item: any = {
...sub
...sub,
};
list.push(item);
@@ -40,7 +40,7 @@ export function setupCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach(async (to) => {
router.beforeEach(async to => {
const settingStore = useSettingStore();
await settingStore.initOnce();
@@ -53,7 +53,7 @@ export function setupCommonGuard(router: Router) {
return true;
});
router.afterEach((to) => {
router.afterEach(to => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path);
@@ -71,6 +71,10 @@ export function setupCommonGuard(router: Router) {
*/
function setupAccessGuard(router: Router) {
router.beforeEach(async (to, from) => {
if (to.matched && to.matched.length > 2) {
to.matched.splice(1, to.matched.length - 2);
}
const accessStore = useAccessStore();
// 是否已经生成过动态路由
if (!accessStore.isAccessChecked) {
@@ -93,7 +97,7 @@ function setupAccessGuard(router: Router) {
}
// 基本路由,这些路由不需要进入权限拦截
const needAuth = to.matched.some((r) => {
const needAuth = to.matched.some(r => {
return r.meta?.auth || r.meta?.permission;
});
@@ -108,7 +112,7 @@ function setupAccessGuard(router: Router) {
// 如不需要,直接删除 query
query: to.fullPath === DEFAULT_HOME_PATH ? {} : { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true
replace: true,
};
}
return true;

View File

@@ -40,11 +40,11 @@ function transformOneResource(resource: any, parent: any) {
if (route.component == null) {
route.component = LayoutPass;
}
if (route?.meta?.cache !== true) {
if (route?.meta?.keepAlive !== true) {
if (route.meta == null) {
route.meta = {};
}
route.meta.cache = false;
route.meta.keepAlive = false;
}
}
if (resource.children) {
@@ -58,7 +58,7 @@ function transformOneResource(resource: any, parent: any) {
}
return {
menu,
route
route,
};
}
@@ -79,7 +79,7 @@ export const buildMenusAndRouters = (resources: any, parent: any = null) => {
setIndex(menus);
return {
routes,
menus
menus,
};
};

View File

@@ -4,7 +4,7 @@ import type { RouteRecordRaw } from "vue-router";
import { mergeRouteModules } from "/@/vben/utils";
const dynamicRouteFiles = import.meta.glob("./modules/**/*.ts*", {
eager: true
eager: true,
});
/** 动态路由 */
@@ -18,7 +18,7 @@ export const frameworkResource = [
component: LayoutBasic,
meta: {
icon: "ion:accessibility",
hideInBreadcrumb: true
hideInBreadcrumb: true,
},
children: [
{
@@ -30,13 +30,13 @@ export const frameworkResource = [
fixedAside: true,
showOnHeader: false,
icon: "ion:home-outline",
auth: true
}
auth: true,
},
},
// @ts-ignore
...dynamicRoutes
]
}
...dynamicRoutes,
],
},
];
console.assert(frameworkResource.length === 1, "frameworkResource数组长度只能为1你只能配置framework路由的子路由");

View File

@@ -10,7 +10,7 @@ export const certdResources = [
meta: {
icon: "ion:key-outline",
auth: true,
order: 0
order: 0,
},
children: [
{
@@ -20,8 +20,8 @@ export const certdResources = [
component: "/certd/pipeline/index.vue",
meta: {
icon: "ion:analytics-sharp",
cache: true
}
keepAlive: true,
},
},
{
title: "编辑流水线",
@@ -29,8 +29,8 @@ export const certdResources = [
path: "/certd/pipeline/detail",
component: "/certd/pipeline/detail.vue",
meta: {
isMenu: false
}
isMenu: false,
},
},
{
title: "执行历史记录",
@@ -39,8 +39,8 @@ export const certdResources = [
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline",
cache: true
}
keepAlive: true,
},
},
{
title: "证书仓库",
@@ -50,8 +50,9 @@ export const certdResources = [
meta: {
icon: "ion:shield-checkmark-outline",
auth: true,
isMenu: true
}
isMenu: true,
keepAlive: true,
},
},
{
title: "站点证书监控",
@@ -60,8 +61,9 @@ export const certdResources = [
component: "/certd/monitor/site/index.vue",
meta: {
icon: "ion:videocam-outline",
auth: true
}
auth: true,
keepAlive: true,
},
},
{
title: "设置",
@@ -71,7 +73,7 @@ export const certdResources = [
meta: {
icon: "ion:settings-outline",
auth: true,
cache: true
keepAlive: true,
},
children: [
{
@@ -82,8 +84,8 @@ export const certdResources = [
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
keepAlive: true,
},
},
{
title: "CNAME记录管理",
@@ -92,8 +94,20 @@ export const certdResources = [
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true
}
auth: true,
keepAlive: true,
},
},
{
title: "子域名托管设置",
name: "SubDomain",
path: "/certd/pipeline/subDomain",
component: "/certd/pipeline/sub-domain/index.vue",
meta: {
icon: "material-symbols:approval-delegation-outline",
auth: true,
keepAlive: true,
},
},
{
title: "流水线分组管理",
@@ -102,8 +116,9 @@ export const certdResources = [
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true
}
auth: true,
keepAlive: true,
},
},
{
@@ -114,8 +129,8 @@ export const certdResources = [
meta: {
icon: "hugeicons:api",
auth: true,
cache: true
}
keepAlive: true,
},
},
{
title: "通知设置",
@@ -125,8 +140,8 @@ export const certdResources = [
meta: {
icon: "ion:megaphone-outline",
auth: true,
cache: true
}
keepAlive: true,
},
},
{
title: "账号信息",
@@ -136,10 +151,10 @@ export const certdResources = [
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false
}
}
]
isMenu: false,
},
},
],
},
{
@@ -153,7 +168,7 @@ export const certdResources = [
return settingStore.isComm && settingStore.isSuiteEnabled;
},
icon: "ion:cart-outline",
auth: true
auth: true,
},
children: [
{
@@ -167,8 +182,8 @@ export const certdResources = [
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true
}
auth: true,
},
},
{
title: "套餐购买",
@@ -181,8 +196,8 @@ export const certdResources = [
return settingStore.isComm;
},
icon: "ion:cart-outline",
auth: true
}
auth: true,
},
},
{
title: "我的订单",
@@ -195,8 +210,9 @@ export const certdResources = [
return settingStore.isComm;
},
icon: "ion:bag-check-outline",
auth: true
}
auth: true,
keepAlive: true,
},
},
{
title: "支付返回",
@@ -206,11 +222,11 @@ export const certdResources = [
meta: {
icon: "ant-design:pay-circle-outlined",
auth: false,
isMenu: false
}
}
]
}
isMenu: false,
},
},
],
},
// {
// title: "邮箱设置",
@@ -222,8 +238,8 @@ export const certdResources = [
// auth: true
// }
// },
]
}
],
},
];
export default certdResources;

View File

@@ -11,7 +11,7 @@ export const sysResources = [
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
order: 10
order: 10,
},
children: [
{
@@ -25,8 +25,8 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:speedometer-outline",
permission: "sys:auth:user:view"
}
permission: "sys:auth:user:view",
},
},
{
@@ -36,8 +36,8 @@ export const sysResources = [
component: "/sys/settings/index.vue",
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
},
},
{
title: "CNAME服务设置",
@@ -46,8 +46,9 @@ export const sysResources = [
component: "/sys/cname/provider/index.vue",
meta: {
icon: "ion:earth-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "邮件服务器设置",
@@ -57,8 +58,8 @@ export const sysResources = [
meta: {
permission: "sys:settings:view",
icon: "ion:mail-outline",
auth: true
}
auth: true,
},
},
{
title: "站点个性化",
@@ -71,8 +72,8 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:document-text-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
},
},
{
title: "顶部菜单设置",
@@ -85,8 +86,9 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:menu",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "系统级授权",
@@ -99,8 +101,9 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:disc-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "插件管理",
@@ -108,13 +111,22 @@ export const sysResources = [
path: "/sys/plugin",
component: "/sys/plugin/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:extension-puzzle-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "编辑插件",
name: "SysPluginEdit",
path: "/sys/plugin/edit",
component: "/sys/plugin/edit.vue",
meta: {
isMenu: false,
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "证书插件配置",
@@ -127,8 +139,8 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:extension-puzzle",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
},
},
{
title: "账号绑定",
@@ -137,8 +149,9 @@ export const sysResources = [
component: "/sys/account/index.vue",
meta: {
icon: "ion:golf-outline",
permission: "sys:settings:view"
}
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "权限管理",
@@ -148,8 +161,9 @@ export const sysResources = [
meta: {
icon: "ion:list-outline",
//需要校验权限
permission: "sys:auth:per:view"
}
permission: "sys:auth:per:view",
keepAlive: true,
},
},
{
title: "角色管理",
@@ -158,8 +172,9 @@ export const sysResources = [
component: "/sys/authority/role/index.vue",
meta: {
icon: "ion:people-outline",
permission: "sys:auth:role:view"
}
permission: "sys:auth:role:view",
keepAlive: true,
},
},
{
title: "用户管理",
@@ -168,8 +183,9 @@ export const sysResources = [
component: "/sys/authority/user/index.vue",
meta: {
icon: "ion:person-outline",
permission: "sys:auth:user:view"
}
permission: "sys:auth:user:view",
keepAlive: true,
},
},
{
@@ -183,7 +199,8 @@ export const sysResources = [
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
}
},
keepAlive: true,
},
children: [
{
@@ -197,8 +214,8 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:cart",
permission: "sys:settings:edit"
}
permission: "sys:settings:edit",
},
},
{
title: "订单管理",
@@ -211,8 +228,9 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:bag-check",
permission: "sys:settings:edit"
}
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "用户套餐",
@@ -225,13 +243,14 @@ export const sysResources = [
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true
}
}
]
}
]
}
auth: true,
keepAlive: true,
},
},
],
},
],
},
];
export default sysResources;

View File

@@ -60,3 +60,9 @@
footer{
background-color: hsl(var(--card)) !important;
}
.ant-select-multiple .ant-select-selection-item-remove{
display: flex;
align-items: center;
}

View File

@@ -93,7 +93,7 @@ body a{
}
span.fs-icon-svg{
display: flex;
display: inline-flex;
align-items: center;
}

View File

@@ -32,7 +32,7 @@ export default {
},
async sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
return new Promise(resolve => setTimeout(resolve, ms));
},
maxLength(str?: string, length = 100) {
@@ -42,6 +42,9 @@ export default {
return "";
},
transformLink(desc: string = "") {
if (!desc) {
return "";
}
return desc.replace(/\[(.*)\]\((.*)\)/g, '<a href="$2" target="_blank">$1</a>');
}
},
};

View File

@@ -88,7 +88,7 @@ function wrapperMenus(menus: MenuRecordRaw[], deep: boolean = true) {
? mapTree(menus, (item: any) => {
return { ...cloneDeep(item), name: $t(item.name) };
})
: menus.map((item) => {
: menus.map(item => {
return { ...cloneDeep(item), name: $t(item.name) };
});
}
@@ -96,8 +96,8 @@ function wrapperMenus(menus: MenuRecordRaw[], deep: boolean = true) {
function toggleSidebar() {
updatePreferences({
sidebar: {
hidden: !preferences.sidebar.hidden
}
hidden: !preferences.sidebar.hidden,
},
});
}
@@ -107,12 +107,12 @@ function clearPreferencesAndLogout() {
watch(
() => preferences.app.layout,
async (val) => {
async val => {
if (val === "sidebar-mixed-nav" && preferences.sidebar.hidden) {
updatePreferences({
sidebar: {
hidden: false
}
hidden: false,
},
});
}
}
@@ -124,7 +124,7 @@ watch(i18n.global.locale, refresh, { flush: "post" });
const slots: SetupContext["slots"] = useSlots();
const headerSlots = computed(() => {
return Object.keys(slots).filter((key) => key.startsWith("header-"));
return Object.keys(slots).filter(key => key.startsWith("header-"));
});
</script>

View File

@@ -20,14 +20,14 @@ const defaultPreferences: Preferences = {
loginExpiredMode: "page",
name: "",
preferencesButtonPosition: "auto",
watermark: false
watermark: false,
},
breadcrumb: {
enable: true,
hideOnlyOne: false,
showHome: false,
showIcon: true,
styleType: "normal"
styleType: "normal",
},
copyright: {
companyName: "greper",
@@ -36,33 +36,33 @@ const defaultPreferences: Preferences = {
enable: false,
icp: "",
icpLink: "",
settingShow: false
settingShow: false,
},
footer: {
enable: true,
fixed: false
fixed: false,
},
header: {
enable: true,
hidden: false,
menuAlign: "start",
mode: "fixed"
mode: "fixed",
},
logo: {
enable: true,
source: "./static/images/logo/logo.svg"
source: "./static/images/logo/logo.svg",
},
navigation: {
accordion: true,
split: true,
styleType: "rounded"
styleType: "rounded",
},
shortcutKeys: {
enable: true,
globalLockScreen: true,
globalLogout: true,
globalPreferences: true,
globalSearch: true
globalSearch: true,
},
sidebar: {
autoActivateChild: true,
@@ -72,7 +72,7 @@ const defaultPreferences: Preferences = {
expandOnHover: true,
extraCollapse: false,
hidden: false,
width: 224
width: 224,
},
tabbar: {
draggable: true,
@@ -86,7 +86,7 @@ const defaultPreferences: Preferences = {
showMaximize: true,
showMore: true,
styleType: "chrome",
wheelable: true
wheelable: true,
},
theme: {
builtinType: "default",
@@ -97,13 +97,13 @@ const defaultPreferences: Preferences = {
mode: "light",
radius: "0.5",
semiDarkHeader: false,
semiDarkSidebar: false
semiDarkSidebar: false,
},
transition: {
enable: true,
loading: false,
name: "fade-slide",
progress: true
progress: true,
},
widget: {
fullscreen: true,
@@ -113,8 +113,8 @@ const defaultPreferences: Preferences = {
notification: false,
refresh: true,
sidebarToggle: true,
themeToggle: true
}
themeToggle: true,
},
};
export { defaultPreferences };

View File

@@ -45,7 +45,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* Close tabs in bulk
*/
async _bulkCloseByPaths(paths: string[]) {
this.tabs = this.tabs.filter((item) => {
this.tabs = this.tabs.filter(item => {
return !paths.includes(getTabPath(item));
});
@@ -60,7 +60,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
if (isAffixTab(tab)) {
return;
}
const index = this.tabs.findIndex((item) => item.fullPath === fullPath);
const index = this.tabs.findIndex(item => item.fullPath === fullPath);
index !== -1 && this.tabs.splice(index, 1);
},
/**
@@ -85,7 +85,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
const toParams = {
params: params || {},
path,
query: query || {}
query: query || {},
};
await router.replace(toParams);
},
@@ -99,7 +99,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
return;
}
const tabIndex = this.tabs.findIndex((tab) => {
const tabIndex = this.tabs.findIndex(tab => {
return getTabPath(tab) === getTabPath(routeTab);
});
@@ -109,13 +109,13 @@ export const useTabbarStore = defineStore("core-tabbar", {
const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ?? -1) as number;
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
// 获取到已经打开的动态路由数, 判断是否大于某一个值
if (maxNumOfOpenTab > 0 && this.tabs.filter((tab) => tab.name === routeTab.name).length >= maxNumOfOpenTab) {
if (maxNumOfOpenTab > 0 && this.tabs.filter(tab => tab.name === routeTab.name).length >= maxNumOfOpenTab) {
// 关闭第一个
const index = this.tabs.findIndex((item) => item.name === routeTab.name);
const index = this.tabs.findIndex(item => item.name === routeTab.name);
index !== -1 && this.tabs.splice(index, 1);
} else if (maxCount > 0 && this.tabs.length >= maxCount) {
// 关闭第一个
const index = this.tabs.findIndex((item) => !Reflect.has(item.meta, "affixTab") || !item.meta.affixTab);
const index = this.tabs.findIndex(item => !Reflect.has(item.meta, "affixTab") || !item.meta.affixTab);
index !== -1 && this.tabs.splice(index, 1);
}
this.tabs.push(tab);
@@ -125,7 +125,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
const mergedTab = {
...currentTab,
...tab,
meta: { ...currentTab?.meta, ...tab.meta }
meta: { ...currentTab?.meta, ...tab.meta },
};
if (currentTab) {
const curMeta = currentTab.meta;
@@ -145,7 +145,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @zh_CN 关闭所有标签页
*/
async closeAllTabs(router: Router) {
const newTabs = this.tabs.filter((tab) => isAffixTab(tab));
const newTabs = this.tabs.filter(tab => isAffixTab(tab));
this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);
await this._goToDefaultTab(router);
this.updateCacheTabs();
@@ -155,7 +155,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param tab
*/
async closeLeftTabs(tab: TabDefinition) {
const index = this.tabs.findIndex((item) => getTabPath(item) === getTabPath(tab));
const index = this.tabs.findIndex(item => getTabPath(item) === getTabPath(tab));
if (index < 1) {
return;
@@ -176,13 +176,13 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param tab
*/
async closeOtherTabs(tab: TabDefinition) {
const closePaths = this.tabs.map((item) => getTabPath(item));
const closePaths = this.tabs.map(item => getTabPath(item));
const paths: string[] = [];
for (const path of closePaths) {
if (path !== tab.fullPath) {
const closeTab = this.tabs.find((item) => getTabPath(item) === path);
const closeTab = this.tabs.find(item => getTabPath(item) === path);
if (!closeTab) {
continue;
}
@@ -198,7 +198,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param tab
*/
async closeRightTabs(tab: TabDefinition) {
const index = this.tabs.findIndex((item) => getTabPath(item) === getTabPath(tab));
const index = this.tabs.findIndex(item => getTabPath(item) === getTabPath(tab));
if (index !== -1 && index < this.tabs.length - 1) {
const rightTabs = this.tabs.slice(index + 1);
@@ -227,7 +227,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
this.updateCacheTabs();
return;
}
const index = this.getTabs.findIndex((item) => getTabPath(item) === getTabPath(currentRoute.value));
const index = this.getTabs.findIndex(item => getTabPath(item) === getTabPath(currentRoute.value));
const before = this.getTabs[index - 1];
const after = this.getTabs[index + 1];
@@ -252,7 +252,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
*/
async closeTabByKey(key: string, router: Router) {
const originKey = decodeURIComponent(key);
const index = this.tabs.findIndex((item) => getTabPath(item) === originKey);
const index = this.tabs.findIndex(item => getTabPath(item) === originKey);
if (index === -1) {
return;
}
@@ -268,7 +268,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param path
*/
getTabByPath(path: string) {
return this.getTabs.find((item) => getTabPath(item) === path) as TabDefinition;
return this.getTabs.find(item => getTabPath(item) === path) as TabDefinition;
},
/**
* @zh_CN 新窗口打开标签页
@@ -283,7 +283,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param tab
*/
async pinTab(tab: TabDefinition) {
const index = this.tabs.findIndex((item) => getTabPath(item) === getTabPath(tab));
const index = this.tabs.findIndex(item => getTabPath(item) === getTabPath(tab));
if (index !== -1) {
const oldTab = this.tabs[index];
tab.meta.affixTab = true;
@@ -292,9 +292,9 @@ export const useTabbarStore = defineStore("core-tabbar", {
this.tabs.splice(index, 1, tab);
}
// 过滤固定tabs后面更改affixTabOrder的值的话可能会有问题目前行464排序affixTabs没有设置值
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
const affixTabs = this.tabs.filter(tab => isAffixTab(tab));
// 获得固定tabs的index
const newIndex = affixTabs.findIndex((item) => getTabPath(item) === getTabPath(tab));
const newIndex = affixTabs.findIndex(item => getTabPath(item) === getTabPath(tab));
// 交换位置重新排序
await this.sortTabs(index, newIndex);
},
@@ -310,7 +310,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
this.renderRouteView = false;
startProgress();
await new Promise((resolve) => setTimeout(resolve, 200));
await new Promise(resolve => setTimeout(resolve, 200));
this.excludeCachedTabs.delete(name as string);
this.renderRouteView = true;
@@ -324,7 +324,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
if (tab?.meta?.newTabTitle) {
return;
}
const findTab = this.tabs.find((item) => getTabPath(item) === getTabPath(tab));
const findTab = this.tabs.find(item => getTabPath(item) === getTabPath(tab));
if (findTab) {
findTab.meta.newTabTitle = undefined;
await this.updateCacheTabs();
@@ -348,7 +348,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param title
*/
async setTabTitle(tab: TabDefinition, title: string) {
const findTab = this.tabs.find((item) => getTabPath(item) === getTabPath(tab));
const findTab = this.tabs.find(item => getTabPath(item) === getTabPath(tab));
if (findTab) {
findTab.meta.newTabTitle = title;
@@ -389,7 +389,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
* @param tab
*/
async unpinTab(tab: TabDefinition) {
const index = this.tabs.findIndex((item) => getTabPath(item) === getTabPath(tab));
const index = this.tabs.findIndex(item => getTabPath(item) === getTabPath(tab));
if (index !== -1) {
const oldTab = this.tabs[index];
@@ -399,7 +399,7 @@ export const useTabbarStore = defineStore("core-tabbar", {
this.tabs.splice(index, 1, tab);
}
// 过滤固定tabs后面更改affixTabOrder的值的话可能会有问题目前行464排序affixTabs没有设置值
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
const affixTabs = this.tabs.filter(tab => isAffixTab(tab));
// 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置
const newIndex = affixTabs.length;
// 交换位置重新排序
@@ -428,11 +428,11 @@ export const useTabbarStore = defineStore("core-tabbar", {
cacheMap.add(name);
}
this.cachedTabs = cacheMap;
}
},
},
getters: {
affixTabs(): TabDefinition[] {
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
const affixTabs = this.tabs.filter(tab => isAffixTab(tab));
return affixTabs.sort((a, b) => {
const orderA = (a.meta?.affixTabOrder ?? 0) as number;
@@ -447,16 +447,16 @@ export const useTabbarStore = defineStore("core-tabbar", {
return [...this.excludeCachedTabs];
},
getTabs(): TabDefinition[] {
const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));
const normalTabs = this.tabs.filter(tab => !isAffixTab(tab));
return [...this.affixTabs, ...normalTabs].filter(Boolean);
}
},
},
persist: [
// tabs不需要保存在localStorage
{
pick: ["tabs"],
storage: sessionStorage
}
storage: sessionStorage,
},
],
state: (): TabbarState => ({
cachedTabs: new Set(),
@@ -464,8 +464,8 @@ export const useTabbarStore = defineStore("core-tabbar", {
excludeCachedTabs: new Set(),
renderRouteView: true,
tabs: [],
updateTime: Date.now()
})
updateTime: Date.now(),
}),
});
// 解决热更新问题
@@ -486,16 +486,16 @@ function cloneTab(route: TabDefinition): TabDefinition {
return {
...opt,
matched: (matched
? matched.map((item) => ({
? matched.map(item => ({
meta: item.meta,
name: item.name,
path: item.path
path: item.path,
}))
: undefined) as RouteRecordNormalized[],
meta: {
...meta,
newTabTitle: meta.newTabTitle
}
newTabTitle: meta.newTabTitle,
},
};
}
@@ -513,7 +513,7 @@ function isAffixTab(tab: TabDefinition) {
*/
function isTabShown(tab: TabDefinition) {
const matched = tab?.matched ?? [];
return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);
return !tab.meta.hideInTab && matched.every(item => !item.meta.hideInTab);
}
/**
@@ -528,6 +528,6 @@ function routeToTab(route: RouteRecordNormalized) {
return {
meta: route.meta,
name: route.name,
path: route.path
path: route.path,
} as TabDefinition;
}

View File

@@ -32,8 +32,8 @@ export default defineComponent({
return {
crudBinding,
crudRef
crudRef,
};
}
},
});
</script>

View File

@@ -45,24 +45,24 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
delRequest,
},
tabs: {
name: "status",
show: true
show: true,
},
rowHandle: {
minWidth: 200,
fixed: "right"
fixed: "right",
},
columns: {
id: {
@@ -70,46 +70,46 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id",
type: "number",
column: {
width: 80
width: 80,
},
form: {
show: false
}
show: false,
},
},
domain: {
title: "被代理域名",
type: "text",
search: {
show: true
show: true,
},
editForm: {
component: {
disabled: true
}
}
disabled: true,
},
},
},
hostRecord: {
title: "主机记录",
type: "text",
form: {
show: false
show: false,
},
column: {
width: 250,
cellRender: ({ value }) => {
return <fs-copyable v-model={value} />;
}
}
},
},
},
recordValue: {
title: "请设置CNAME",
type: "copyable",
form: {
show: false
show: false,
},
column: {
width: 500
}
width: 500,
},
},
cnameProviderId: {
title: "CNAME服务",
@@ -117,7 +117,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
dict: dict({
url: "/cname/provider/list",
value: "id",
label: "domain"
label: "domain",
}),
form: {
component: {
@@ -141,7 +141,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} else {
return item.domain;
}
}
},
},
helper: {
render() {
@@ -156,8 +156,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
</router-link>
</div>
);
}
}
},
},
},
column: {
width: 120,
@@ -168,8 +168,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} else {
return <a-tag color={"blue"}>CNAME</a-tag>;
}
}
}
},
},
},
status: {
title: "状态",
@@ -180,22 +180,22 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" }
]
{ label: "验证超时", value: "timeout", color: "red" },
],
}),
addForm: {
show: false
show: false,
},
column: {
width: 120,
align: "center"
}
align: "center",
},
},
triggerValidate: {
title: "验证",
type: "text",
form: {
show: false
show: false,
},
column: {
conditionalRenderDisabled: true,
@@ -235,32 +235,32 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
<CnameTip record={row} />
</div>
);
}
}
},
},
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
show: false,
},
column: {
sorter: true,
width: 160,
align: "center"
}
align: "center",
},
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false
show: false,
},
column: {
show: true
}
}
}
}
show: true,
},
},
},
},
};
}

View File

@@ -19,14 +19,14 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
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";
defineOptions({
name: "CnameRecord"
name: "CnameRecord",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
@@ -41,7 +41,7 @@ const handleBatchDelete = () => {
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
}
},
});
} else {
message.error("请先勾选记录");
@@ -52,7 +52,8 @@ const handleBatchDelete = () => {
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less">
</style>
<style lang="less"></style>

View File

@@ -14,14 +14,14 @@
</template>
<script lang="ts" setup>
import {onActivated, onMounted} from "vue";
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";
defineOptions({
name: "PipelineHistory"
name: "PipelineHistory",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
@@ -36,7 +36,7 @@ const handleBatchDelete = () => {
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
}
},
});
} else {
message.error("请先勾选记录");

View File

@@ -40,11 +40,11 @@ const { buildFormOptions } = useColumns();
const passwordFormOptions: CrudOptions = {
form: {
col: {
span: 24
span: 24,
},
wrapper: {
title: "修改密码",
width: "500px"
width: "500px",
},
async doSubmit({ form }) {
await api.changePassword(form);
@@ -53,15 +53,15 @@ const passwordFormOptions: CrudOptions = {
},
async afterSubmit() {
notification.success({ message: "修改成功" });
}
},
},
columns: {
password: {
title: "旧密码",
type: "password",
form: {
rules: [{ required: true, message: "请输入旧密码" }]
}
rules: [{ required: true, message: "请输入旧密码" }],
},
},
newPassword: {
title: "新密码",
@@ -70,9 +70,9 @@ const passwordFormOptions: CrudOptions = {
rules: [
{ required: true, message: "请输入确认密码" },
//@ts-ignore
{ validator: validatePass1, trigger: "blur" }
]
}
{ validator: validatePass1, trigger: "blur" },
],
},
},
confirmNewPassword: {
title: "确认新密码",
@@ -81,11 +81,11 @@ const passwordFormOptions: CrudOptions = {
rules: [
{ required: true, message: "请输入确认密码" },
//@ts-ignore
{ validator: validatePass2, trigger: "blur" }
]
}
}
}
{ validator: validatePass2, trigger: "blur" },
],
},
},
},
};
async function open(opts: { password: "" }) {
@@ -93,13 +93,13 @@ async function open(opts: { password: "" }) {
formOptions.newInstance = true; //新实例打开
passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({
password: opts.password
password: opts.password,
});
console.log(passwordFormRef.value);
}
const scope = ref({
open: open
open: open,
});
defineExpose(scope.value);

View File

@@ -24,7 +24,7 @@ import { siteInfoApi } from "./api";
import { Modal, notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/modules/settings";
defineOptions({
name: "SiteCertMonitor"
name: "SiteCertMonitor",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
const settingStore = useSettingStore();
@@ -36,9 +36,9 @@ function checkAll() {
await siteInfoApi.CheckAll();
notification.success({
message: "检查任务已提交",
description: "请稍后刷新页面查看结果"
description: "请稍后刷新页面查看结果",
});
}
},
});
}

View File

@@ -34,8 +34,8 @@ export default defineComponent({
return {
crudBinding,
crudRef
crudRef,
};
}
},
});
</script>

View File

@@ -33,32 +33,32 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
pageRequest,
addRequest,
editRequest,
delRequest
delRequest,
},
search: {
show: false
show: false,
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px"
}
width: "100px",
},
},
col: {
span: 22
span: 22,
},
wrapper: {
width: 600
}
width: 600,
},
},
actionbar: {
buttons: {
add: {
text: "生成新的Key"
}
}
text: "生成新的Key",
},
},
},
rowHandle: {
width: 300,
@@ -96,11 +96,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
</div>
</div>
);
}
},
});
}
}
}
},
},
},
},
columns: {
id: {
@@ -108,42 +108,42 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id",
type: "number",
search: {
show: false
show: false,
},
column: {
width: 100,
editable: {
disabled: true
}
disabled: true,
},
},
form: {
show: false
}
show: false,
},
},
keyId: {
title: "KeyId",
type: ["text", "copyable"],
search: {
show: true
show: true,
},
form: {
show: false
show: false,
},
column: {
width: 250,
sorter: true
}
sorter: true,
},
},
keySecret: {
title: "KeySecret",
type: ["text", "copyable"],
form: {
show: false
show: false,
},
column: {
width: 580,
sorter: true
}
sorter: true,
},
},
scope: {
title: "权限范围",
@@ -151,8 +151,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
dict: dict({
data: [
{ label: "仅开放接口", value: "open", color: "blue" },
{ label: "账户所有权限", value: "user", color: "red" }
]
{ label: "账户所有权限", value: "user", color: "red" },
],
}),
form: {
value: "open",
@@ -160,26 +160,26 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
rules: [{ required: true, message: "此项必填" }],
helper: "仅开放接口只可以访问开放接口,账户所有权限可以访问所有接口",
component: {
vModel: "value"
}
vModel: "value",
},
},
column: {
width: 120,
align: "center",
sorter: true
}
sorter: true,
},
},
createTime: {
title: "创建时间",
type: "datetime",
search: {
show: false
show: false,
},
form: {
show: false
}
}
}
}
show: false,
},
},
},
},
};
}

View File

@@ -17,7 +17,7 @@ import createCrudOptions from "./crud";
import { OPEN_API_DOC } from "/@/views/certd/open/openkey/api";
defineOptions({
name: "OpenKey"
name: "OpenKey",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });

View File

@@ -37,7 +37,6 @@ const domain = computed(() => {
});
function onUpdated(res: { uploadCert: any }) {
debugger;
emit("update:modelValue", res.uploadCert);
const domains = getAllDomainsFromCrt(res.uploadCert.crt);
emit("updated", { domains });
@@ -45,7 +44,6 @@ function onUpdated(res: { uploadCert: any }) {
const pipeline: any = inject("pipeline");
function onUploadClick() {
debugger;
openUpdateCertDialog({
onSubmit: onUpdated,
});

View File

@@ -1,15 +1,11 @@
import { compute, CreateCrudOptionsRet, dict } from "@fast-crud/fast-crud";
import { useReference } from "/@/use/use-refrence";
import _, { merge } from "lodash-es";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import { merge, cloneDeep } from "lodash-es";
import * as api from "../api.plugin";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOptionsRet {
const inputs: any = {};
const userStore = useUserStore();
const settingStore = useSettingStore();
const moreParams = [];
for (const plugin of certPlugins) {
for (const inputKey in plugin.input) {
@@ -18,7 +14,7 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
inputs[inputKey].form.show = true;
continue;
}
const inputDefine = _.cloneDeep(plugin.input[inputKey]);
const inputDefine = cloneDeep(plugin.input[inputKey]);
if (!inputDefine.required && !inputDefine.maybeNeed) {
moreParams.push(inputKey);
// continue;

View File

@@ -162,8 +162,9 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
},
uploadCert: {
order: 2,
text: "上传证书部署",
text: "商用证书托管",
type: "primary",
title: "手动上传自有证书,执行自动部署(证书有更新时,都需要手动上传一次)",
icon: "ion:cloud-upload-outline",
click() {
openUploadCreateDialog();

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