Compare commits

...

60 Commits

Author SHA1 Message Date
xiaojunnuo
86ca35ce33 v1.31.6 2025-03-24 23:51:00 +08:00
xiaojunnuo
675ab31305 build: prepare to build 2025-03-24 23:48:42 +08:00
xiaojunnuo
83c2d743bc chore: 2025-03-24 23:48:34 +08:00
xiaojunnuo
2c87b3d906 build: prepare to build 2025-03-24 23:47:10 +08:00
xiaojunnuo
05b6159802 perf: 上传到主机支持scp方式 2025-03-24 23:45:45 +08:00
xiaojunnuo
c56f48c1e3 perf: 优化图标 2025-03-24 21:27:31 +08:00
xiaojunnuo
90b045af6d fix: 修复dns.la无法申请证书的bug 2025-03-24 09:38:18 +08:00
xiaojunnuo
5cccb21175 chore: 2025-03-24 00:10:01 +08:00
xiaojunnuo
1a71969403 chore: 2025-03-24 00:05:19 +08:00
xiaojunnuo
389a1fbd04 build: publish 2025-03-22 15:05:31 +08:00
xiaojunnuo
ca02ae6183 build: trigger build image 2025-03-22 15:05:11 +08:00
xiaojunnuo
92446cb048 v1.31.5 2025-03-22 15:03:34 +08:00
xiaojunnuo
954ce4533f build: prepare to build 2025-03-22 15:01:00 +08:00
xiaojunnuo
f7b88f9e3b fix: 修复通知选择器无法选择的bug
https://github.com/certd/certd/issues/351
2025-03-22 15:00:17 +08:00
xiaojunnuo
736fe038eb fix: 修复证书流水线创建失败的bug 2025-03-22 14:59:54 +08:00
xiaojunnuo
abcd257db0 build: publish 2025-03-22 02:10:40 +08:00
xiaojunnuo
c38b5f3cdc build: trigger build image 2025-03-22 02:10:22 +08:00
xiaojunnuo
b649617e04 v1.31.4 2025-03-22 02:09:07 +08:00
xiaojunnuo
a4e2287101 build: prepare to build 2025-03-22 02:06:56 +08:00
xiaojunnuo
fbb66f3c43 perf: 手动上传证书部署流水线 2025-03-22 02:06:02 +08:00
xiaojunnuo
fedf90ea78 chore: 2025-03-21 23:40:31 +08:00
xiaojunnuo
d558d50102 chore: 2025-03-21 23:11:58 +08:00
xiaojunnuo
656cb89fe8 chore: 2025-03-21 12:23:59 +08:00
xiaojunnuo
1e6ddd250e chore: 2025-03-21 11:08:58 +08:00
xiaojunnuo
1de8eee6ea fix: 修复dns.la域名申请失败的bug 2025-03-21 11:07:15 +08:00
xiaojunnuo
425bba67c5 perf: 流水线增加上传证书快捷方式 2025-03-21 01:02:57 +08:00
xiaojunnuo
8b0daf7200 chore: 2025-03-20 23:19:14 +08:00
xiaojunnuo
589a373142 perf: 宝塔支持doker站点证书部署 2025-03-20 23:09:36 +08:00
xiaojunnuo
0cfc71e4bf Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-03-19 15:01:55 +08:00
xiaojunnuo
92dabe6276 docs: docs sitemap 2025-03-19 15:01:14 +08:00
xiaojunnuo
d1b61b6bf9 chore: 支持手动上传证书并部署 2025-03-19 00:28:50 +08:00
xiaojunnuo
873f2b618b perf: 保存调整后的列宽 2025-03-18 10:00:16 +08:00
xiaojunnuo
4453070060 chore: 支持手动上传证书并部署 2025-03-18 01:02:20 +08:00
xiaojunnuo
de40be430b chore: 支持手动上传证书并部署 2025-03-18 00:52:50 +08:00
xiaojunnuo
29a6a992f0 chore: 2025-03-17 18:28:33 +08:00
xiaojunnuo
0a7d2d6264 chore: 2025-03-17 18:27:52 +08:00
xiaojunnuo
e09f92f9ee chore: 2025-03-17 18:24:55 +08:00
xiaojunnuo
9be1ecc8aa fix: 修复站点监控通知通过webhook发送失败的bug 2025-03-17 18:20:15 +08:00
xiaojunnuo
729b19c8da perf: 站点监控,手动测试也发通知 2025-03-17 16:55:23 +08:00
xiaojunnuo
a9fffa5180 perf: 支持手动上传证书并部署 2025-03-17 00:19:01 +08:00
xiaojunnuo
0069c0e399 perf: 站点证书监控支持模糊查询 2025-03-17 00:16:56 +08:00
xiaojunnuo
b6fd38e293 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-03-17 00:06:31 +08:00
xiaojunnuo
36aa7f82b0 perf: 创建证书流水线时,支持更多参数展开 2025-03-17 00:06:03 +08:00
xiaojunnuo
d01004d530 perf: 优化选择任务时手机版展示效果 2025-03-16 21:37:57 +08:00
xiaojunnuo
d85a02feeb perf: 流水线页面可以鼠标按住左右拖动 2025-03-16 21:16:14 +08:00
xiaojunnuo
b82e1dcd62 perf: 支持飞书通知 2025-03-14 13:16:48 +08:00
xiaojunnuo
74c6a2266f build: publish 2025-03-14 01:19:31 +08:00
xiaojunnuo
9754223f31 build: trigger build image 2025-03-14 01:19:12 +08:00
xiaojunnuo
cfbbac9796 v1.31.3 2025-03-14 01:17:37 +08:00
xiaojunnuo
fece8955cf build: prepare to build 2025-03-14 01:15:22 +08:00
xiaojunnuo
170b2afb0e perf: 1panel支持 apikey方式授权 2025-03-14 01:14:04 +08:00
xiaojunnuo
ee8af18d0a perf: 支持dns.la 2025-03-14 00:53:31 +08:00
xiaojunnuo
27386ea04d perf: cf授权支持配置http代理 2025-03-14 00:34:31 +08:00
xiaojunnuo
0d71a8ee50 perf: 套餐支持3天7天等选项 2025-03-14 00:28:20 +08:00
xiaojunnuo
82a72e0b49 perf: 支持部署到天翼云CDN 2025-03-14 00:16:34 +08:00
xiaojunnuo
5035c123f0 chore: 2025-03-13 23:05:36 +08:00
xiaojunnuo
474b3372d8 fix: 修复阿里云fc获取不到列表的bug 2025-03-12 14:29:41 +08:00
xiaojunnuo
be87124ada perf: 证书仓库增加有效期显示 2025-03-12 11:15:46 +08:00
xiaojunnuo
aa3032db35 build: publish 2025-03-12 10:27:06 +08:00
xiaojunnuo
a4ead79888 build: trigger build image 2025-03-12 10:26:50 +08:00
171 changed files with 4407 additions and 1551 deletions

View File

@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
### Bug Fixes
* 修复dns.la无法申请证书的bug ([90b045a](https://github.com/certd/certd/commit/90b045af6d1a4f46986e4b118885c1f050df067c))
### Performance Improvements
* 上传到主机支持scp方式 ([05b6159](https://github.com/certd/certd/commit/05b6159802b9e85b6a410361b60b5c28875b48e7))
* 优化图标 ([c56f48c](https://github.com/certd/certd/commit/c56f48c1e3c54c4e203fafb380d9091d75681b7e))
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
### Bug Fixes
* 修复通知选择器无法选择的bug ([f7b88f9](https://github.com/certd/certd/commit/f7b88f9e3b7d9d9122e4fd2003a20c555bd50c7d))
* 修复证书流水线创建失败的bug ([736fe03](https://github.com/certd/certd/commit/736fe038ebda56648bcc4c12884a700341d2c049))
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复站点监控通知通过webhook发送失败的bug ([9be1ecc](https://github.com/certd/certd/commit/9be1ecc8aab3ea23dd0dc2dab3688f4edb90ef2c))
* 修复dns.la域名申请失败的bug ([1de8eee](https://github.com/certd/certd/commit/1de8eee6ea8307f3c11626af75303d3cc104bb95))
### Performance Improvements
* 宝塔支持doker站点证书部署 ([589a373](https://github.com/certd/certd/commit/589a373142ef7f50d64d3aa767a90b1f4b64da93))
* 保存调整后的列宽 ([873f2b6](https://github.com/certd/certd/commit/873f2b618b9d7320045baf69d6da83afe48a780f))
* 创建证书流水线时,支持更多参数展开 ([36aa7f8](https://github.com/certd/certd/commit/36aa7f82b078a053a102331b3c6f132fb9d492f9))
* 流水线页面可以鼠标按住左右拖动 ([d85a02f](https://github.com/certd/certd/commit/d85a02feeb3183c5abd6c1ea790d5923a32d7271))
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
* 优化选择任务时手机版展示效果 ([d01004d](https://github.com/certd/certd/commit/d01004d53071a75ac91ee21cc96bde9369f77ff3))
* 站点监控,手动测试也发通知 ([729b19c](https://github.com/certd/certd/commit/729b19c8da60d5efb5baef7cf8df0518e7f6b471))
* 站点证书监控支持模糊查询 ([0069c0e](https://github.com/certd/certd/commit/0069c0e3992946a8dd6410f299d4fc974ef0e76b))
* 支持飞书通知 ([b82e1dc](https://github.com/certd/certd/commit/b82e1dcd6217b09a7d7e21cd648bb31de320cadf))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Bug Fixes
* 修复阿里云fc获取不到列表的bug ([474b337](https://github.com/certd/certd/commit/474b3372d8ce98e6d45900bf8046bc0b3f220686))
### Performance Improvements
* 1panel支持 apikey方式授权 ([170b2af](https://github.com/certd/certd/commit/170b2afb0e3b125e4ed057f633fe895b5ac3ac22))
* 套餐支持3天7天等选项 ([0d71a8e](https://github.com/certd/certd/commit/0d71a8ee501a0e5bb69decf07e8729026e9d85bf))
* 证书仓库增加有效期显示 ([be87124](https://github.com/certd/certd/commit/be87124ada7a093f281ca29a45c86b4ea4644ead))
* 支持部署到天翼云CDN ([82a72e0](https://github.com/certd/certd/commit/82a72e0b497efa043d342ad0e33c083a2de79a05))
* 支持dns.la ([ee8af18](https://github.com/certd/certd/commit/ee8af18d0ac0af82544d6dda1e4b4c678b733041))
* cf授权支持配置http代理 ([27386ea](https://github.com/certd/certd/commit/27386ea04d3c1a5aebe3cfdd7ac48185eaa76629))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
### Bug Fixes ### Bug Fixes

View File

@@ -1 +1 @@
23:33 15:05

View File

@@ -12,6 +12,9 @@ export default defineConfig({
md.use(lightbox, {}); md.use(lightbox, {});
} }
}, },
sitemap: {
hostname: 'https://certd.docmirror.cn'
},
head: [ head: [
// [ // [
// 'meta', // 'meta',
@@ -25,9 +28,9 @@ export default defineConfig({
name: "keywords", name: "keywords",
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台证书流水线" content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台证书流水线"
}], }],
["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}], // ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" /> //<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}], // ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
["link", { rel: "icon", href: "/static/logo/logo.svg" }] ["link", { rel: "icon", href: "/static/logo/logo.svg" }]
], ],
themeConfig: { themeConfig: {

View File

@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
### Bug Fixes
* 修复通知选择器无法选择的bug ([f7b88f9](https://github.com/certd/certd/commit/f7b88f9e3b7d9d9122e4fd2003a20c555bd50c7d))
* 修复证书流水线创建失败的bug ([736fe03](https://github.com/certd/certd/commit/736fe038ebda56648bcc4c12884a700341d2c049))
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复站点监控通知通过webhook发送失败的bug ([9be1ecc](https://github.com/certd/certd/commit/9be1ecc8aab3ea23dd0dc2dab3688f4edb90ef2c))
* 修复dns.la域名申请失败的bug ([1de8eee](https://github.com/certd/certd/commit/1de8eee6ea8307f3c11626af75303d3cc104bb95))
### Performance Improvements
* 宝塔支持doker站点证书部署 ([589a373](https://github.com/certd/certd/commit/589a373142ef7f50d64d3aa767a90b1f4b64da93))
* 保存调整后的列宽 ([873f2b6](https://github.com/certd/certd/commit/873f2b618b9d7320045baf69d6da83afe48a780f))
* 创建证书流水线时,支持更多参数展开 ([36aa7f8](https://github.com/certd/certd/commit/36aa7f82b078a053a102331b3c6f132fb9d492f9))
* 流水线页面可以鼠标按住左右拖动 ([d85a02f](https://github.com/certd/certd/commit/d85a02feeb3183c5abd6c1ea790d5923a32d7271))
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
* 优化选择任务时手机版展示效果 ([d01004d](https://github.com/certd/certd/commit/d01004d53071a75ac91ee21cc96bde9369f77ff3))
* 站点监控,手动测试也发通知 ([729b19c](https://github.com/certd/certd/commit/729b19c8da60d5efb5baef7cf8df0518e7f6b471))
* 站点证书监控支持模糊查询 ([0069c0e](https://github.com/certd/certd/commit/0069c0e3992946a8dd6410f299d4fc974ef0e76b))
* 支持飞书通知 ([b82e1dc](https://github.com/certd/certd/commit/b82e1dcd6217b09a7d7e21cd648bb31de320cadf))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Bug Fixes
* 修复阿里云fc获取不到列表的bug ([474b337](https://github.com/certd/certd/commit/474b3372d8ce98e6d45900bf8046bc0b3f220686))
### Performance Improvements
* 1panel支持 apikey方式授权 ([170b2af](https://github.com/certd/certd/commit/170b2afb0e3b125e4ed057f633fe895b5ac3ac22))
* 套餐支持3天7天等选项 ([0d71a8e](https://github.com/certd/certd/commit/0d71a8ee501a0e5bb69decf07e8729026e9d85bf))
* 证书仓库增加有效期显示 ([be87124](https://github.com/certd/certd/commit/be87124ada7a093f281ca29a45c86b4ea4644ead))
* 支持部署到天翼云CDN ([82a72e0](https://github.com/certd/certd/commit/82a72e0b497efa043d342ad0e33c083a2de79a05))
* 支持dns.la ([ee8af18](https://github.com/certd/certd/commit/ee8af18d0ac0af82544d6dda1e4b4c678b733041))
* cf授权支持配置http代理 ([27386ea](https://github.com/certd/certd/commit/27386ea04d3c1a5aebe3cfdd7ac48185eaa76629))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
### Bug Fixes
* 修复cname记录查找bug ([95fb4e3](https://github.com/certd/certd/commit/95fb4e3e8be6ca13cc43b451f6141d62190ba453))
## [1.31.1](https://github.com/certd/certd/compare/v1.31.0...v1.31.1) (2025-03-11) ## [1.31.1](https://github.com/certd/certd/compare/v1.31.0...v1.31.1) (2025-03-11)
### Performance Improvements ### Performance Improvements

View File

@@ -6,6 +6,10 @@
## 2. 使用示例 ## 2. 使用示例
```js ```js
// 如果需要引用第三方库必须使用import语法
// const thirdSdk = await import("third-sdk-name")
const certPem = ctx.self.cert.crt const certPem = ctx.self.cert.crt
const certKey = ctx.self.cert.key const certKey = ctx.self.cert.key

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.31.2" "version": "1.31.6"
} }

View File

@@ -9,7 +9,7 @@
"@lerna-lite/run": "^3.9.3", "@lerna-lite/run": "^3.9.3",
"@lerna-lite/version": "^3.9.3", "@lerna-lite/version": "^3.9.3",
"medium-zoom": "^1.1.0", "medium-zoom": "^1.1.0",
"vitepress": "^1.4.1", "vitepress": "^2.0.0-alpha.4",
"vitepress-plugin-lightbox": "^1.0.2" "vitepress-plugin-lightbox": "^1.0.2"
}, },
"scripts": { "scripts": {

View File

@@ -0,0 +1,7 @@
{
"printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/publishlab/node-acme-client/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/acme-client
## [1.31.5](https://github.com/publishlab/node-acme-client/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/acme-client
## [1.31.4](https://github.com/publishlab/node-acme-client/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/acme-client
## [1.31.3](https://github.com/publishlab/node-acme-client/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/acme-client
## [1.31.2](https://github.com/publishlab/node-acme-client/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/publishlab/node-acme-client/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/acme-client **Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"module": "scr/index.js", "module": "scr/index.js",
"main": "src/index.js", "main": "src/index.js",
@@ -18,7 +18,7 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@peculiar/x509": "^1.11.0", "@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"axios": "^1.7.2", "axios": "^1.7.2",
@@ -30,6 +30,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.10", "@types/node": "^20.14.10",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"chai": "^4.4.1", "chai": "^4.4.1",
"chai-as-promised": "^7.1.2", "chai-as-promised": "^7.1.2",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@@ -65,5 +67,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -53,10 +53,10 @@ export default async (client, userOpts) => {
try { try {
client.getAccountUrl(); client.getAccountUrl();
log('[auto] Account URL already exists, skipping account registration'); log('[auto] Account URL already exists, skipping account registration 证书申请账户已存在,跳过注册 ');
} }
catch (e) { catch (e) {
log('[auto] Registering account'); log('[auto] Registering account (注册证书申请账户)');
await client.createAccount(accountPayload); await client.createAccount(accountPayload);
} }
@@ -64,7 +64,7 @@ export default async (client, userOpts) => {
* Parse domains from CSR * 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 { commonName, altNames } = readCsrDomains(opts.csr);
const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d))); const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d)));
@@ -120,20 +120,20 @@ export default async (client, userOpts) => {
// throw new Error('测试异常'); // throw new Error('测试异常');
/* Challenge verification */ /* Challenge verification */
if (opts.skipChallengeVerification === true) { if (opts.skipChallengeVerification === true) {
log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=truewait 60s`); log(`[auto] [${d}] 跳过本地验证(skipChallengeVerification=true),等待 60s`);
await wait(60 * 1000); await wait(60 * 1000);
} }
else { else {
log(`[auto] [${d}] Running challenge verification, type = ${challenge.type}`); log(`[auto] [${d}] 开始本地验证, type = ${challenge.type}`);
try { try {
await client.verifyChallenge(authz, challenge); await client.verifyChallenge(authz, challenge);
} }
catch (e) { catch (e) {
log(`[auto] [${d}] challenge verification threw error: ${e.message}`); log(`[auto] [${d}] 本地验证失败尝试请求ACME提供商获取状态: ${e.message}`);
} }
} }
/* Complete challenge and wait for valid status */ /* Complete challenge and wait for valid status */
log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`); log(`[auto] [${d}] 请求ACME提供商完成验证等待返回valid状态`);
await client.completeChallenge(challenge); await client.completeChallenge(challenge);
challengeCompleted = true; challengeCompleted = true;

View File

@@ -500,7 +500,7 @@ class AcmeClient {
await verify[challenge.type](authz, challenge, keyAuthorization); await verify[challenge.type](authz, challenge, keyAuthorization);
}; };
log('Waiting for ACME challenge verification', this.backoffOpts); log('Waiting for ACME challenge verification等待ACME挑战验证', this.backoffOpts);
return util.retry(verifyFn, this.backoffOpts); return util.retry(verifyFn, this.backoffOpts);
} }
@@ -568,14 +568,14 @@ class AcmeClient {
const resp = await this.api.apiRequest(item.url, null, [200]); const resp = await this.api.apiRequest(item.url, null, [200]);
/* Verify status */ /* Verify status */
log(`Item has status: ${resp.data.status}`); log(`Item has status(挑战状态): ${resp.data.status}`);
if (invalidStates.includes(resp.data.status)) { if (invalidStates.includes(resp.data.status)) {
abort(); abort();
throw new Error(util.formatResponseError(resp)); throw new Error(util.formatResponseError(resp));
} }
else if (pendingStates.includes(resp.data.status)) { else if (pendingStates.includes(resp.data.status)) {
throw new Error('Operation is pending or processing'); throw new Error('Operation is pending or processing(当前仍然在等待状态)');
} }
else if (validStates.includes(resp.data.status)) { else if (validStates.includes(resp.data.status)) {
return resp.data; return resp.data;
@@ -584,7 +584,7 @@ class AcmeClient {
throw new Error(`Unexpected item status: ${resp.data.status}`); throw new Error(`Unexpected item status: ${resp.data.status}`);
}; };
log(`Waiting for valid status from: ${item.url}`, this.backoffOpts); log(`Waiting for valid status 等待valid状态: ${item.url}`, this.backoffOpts);
return util.retry(verifyFn, this.backoffOpts); return util.retry(verifyFn, this.backoffOpts);
} }

View File

@@ -60,8 +60,9 @@ async function retryPromise(fn, attempts, backoff) {
throw e; throw e;
} }
log(`Promise rejected: ${e.message}`);
const duration = backoff.duration(); const duration = backoff.duration();
log(`Promise rejected attempt #${backoff.attempts}, retrying in ${duration}ms: ${e.message}`); log(`attempt #${backoff.attempts}, ${duration}ms 后重试: ${e.message}`);
await new Promise((resolve) => { setTimeout(resolve, duration); }); await new Promise((resolve) => { setTimeout(resolve, duration); });
return retryPromise(fn, attempts, backoff); return retryPromise(fn, attempts, backoff);
@@ -241,7 +242,7 @@ async function resolveDomainBySoaRecord(recordName) {
*/ */
async function getAuthoritativeDnsResolver(recordName) { async function getAuthoritativeDnsResolver(recordName) {
log(`Locating authoritative NS records for name: ${recordName}`); log(`Locating authoritative NS records for name: ${recordName} 获取域名的权威NS服务器`);
const resolver = new dns.Resolver(); const resolver = new dns.Resolver();
try { try {
@@ -249,13 +250,14 @@ async function getAuthoritativeDnsResolver(recordName) {
const domain = await resolveDomainBySoaRecord(recordName); const domain = await resolveDomainBySoaRecord(recordName);
/* Resolve authoritative NS addresses */ /* Resolve authoritative NS addresses */
log(`Looking up authoritative NS records for domain: ${domain}`); log(`Looking up authoritative NS records for domain获取域名的权威NS服务器: ${domain}`);
const nsRecords = await dns.resolveNs(domain); const nsRecords = await dns.resolveNs(domain);
log(`域名权威NS服务器${nsRecords}`);
const nsAddrArray = await Promise.all(nsRecords.map(async (r) => dns.resolve4(r))); const nsAddrArray = await Promise.all(nsRecords.map(async (r) => dns.resolve4(r)));
const nsAddresses = [].concat(...nsAddrArray).filter((a) => a); const nsAddresses = [].concat(...nsAddrArray).filter((a) => a);
if (!nsAddresses.length) { if (!nsAddresses.length) {
throw new Error(`Unable to locate any valid authoritative NS addresses for domain: ${domain}`); throw new Error(`Unable to locate any valid authoritative NS addresses for domain获取权威服务器IP失败: ${domain}`);
} }
/* Authoritative NS success */ /* Authoritative NS success */
@@ -263,12 +265,12 @@ async function getAuthoritativeDnsResolver(recordName) {
resolver.setServers(nsAddresses); resolver.setServers(nsAddresses);
} }
catch (e) { catch (e) {
log(`Authoritative NS lookup error: ${e.message}`); log(`Authoritative NS lookup error获取权威NS服务器地址失败: ${e.message}`);
} }
/* Return resolver */ /* Return resolver */
const addresses = resolver.getServers(); const addresses = resolver.getServers();
log(`DNS resolver addresses: ${addresses.join(', ')}`); log(`DNS resolver addresses域名的权威NS服务器地址: ${addresses.join(', ')}`);
return resolver; return resolver;
} }

View File

@@ -113,14 +113,14 @@ export async function walkTxtRecord(recordName) {
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') { async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
const recordName = `${prefix}${authz.identifier.value}`; const recordName = `${prefix}${authz.identifier.value}`;
log(`Resolving DNS TXT from record: ${recordName}`); log(`Resolving DNS TXT from record解析DNS TXT记录: ${recordName}`);
const recordValues = await walkTxtRecord(recordName); const recordValues = await walkTxtRecord(recordName);
log(`DNS query finished successfully, found ${recordValues.length} TXT records`); log(`DNS query finished successfullyDNS查询成功, found ${recordValues.length} TXT records`);
if (!recordValues.length || !recordValues.includes(keyAuthorization)) { if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
throw new Error(`Authorization not found in DNS TXT record: ${recordName}need:${keyAuthorization},found:${recordValues}`); throw new Error(`Authorization not found in DNS TXT record没有找到需要的DNS TXT记录: ${recordName}need:${keyAuthorization},found:${recordValues}`);
} }
log(`Key authorization match for ${challenge.type}/${recordName}, ACME challenge verified`); log(`Key authorization match for ${challenge.type}/${recordName}, ACME challenge verified(域名所有权校验成功)`);
return true; return true;
} }

View File

@@ -16,7 +16,7 @@
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-empty-function": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }] "@typescript-eslint/no-unused-vars": "off"
} }
} }

View File

@@ -0,0 +1,7 @@
{
"printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/basic
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/basic
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/basic
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Performance Improvements
* 支持部署到天翼云CDN ([82a72e0](https://github.com/certd/certd/commit/82a72e0b497efa043d342ad0e33c083a2de79a05))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/basic **Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
10:23 23:48

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/basic", "name": "@certd/basic",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -33,8 +33,8 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node-forge": "^1.3.2", "@types/node-forge": "^1.3.2",
"@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.59.7", "@typescript-eslint/parser": "^8.26.1",
"chai": "4.3.10", "chai": "4.3.10",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@@ -44,5 +44,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -7,6 +7,10 @@ function sha256(data: string, digest: BinaryToTextEncoding = 'hex') {
return crypto.createHash('sha256').update(data).digest(digest); return crypto.createHash('sha256').update(data).digest(digest);
} }
function hmacSha256(data: string, digest: BinaryToTextEncoding = 'base64') {
return crypto.createHmac('sha256', data).update(Buffer.alloc(0)).digest(digest);
}
function base64(data: string) { function base64(data: string) {
return Buffer.from(data).toString('base64'); return Buffer.from(data).toString('base64');
} }
@@ -14,4 +18,5 @@ export const hashUtils = {
md5, md5,
sha256, sha256,
base64, base64,
hmacSha256,
}; };

View File

@@ -16,7 +16,7 @@
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-empty-function": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }] "@typescript-eslint/no-unused-vars": "off"
} }
} }

View File

@@ -1,3 +1,7 @@
{ {
"printWidth": 160 "printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
} }

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/pipeline
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/pipeline
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复站点监控通知通过webhook发送失败的bug ([9be1ecc](https://github.com/certd/certd/commit/9be1ecc8aab3ea23dd0dc2dab3688f4edb90ef2c))
### Performance Improvements
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 支持飞书通知 ([b82e1dc](https://github.com/certd/certd/commit/b82e1dcd6217b09a7d7e21cd648bb31de320cadf))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/pipeline
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/pipeline **Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/pipeline", "name": "@certd/pipeline",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -16,8 +16,8 @@
"test": "mocha --loader=ts-node/esm" "test": "mocha --loader=ts-node/esm"
}, },
"dependencies": { "dependencies": {
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@certd/plus-core": "^1.31.2", "@certd/plus-core": "^1.31.6",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13" "reflect-metadata": "^0.1.13"
@@ -31,8 +31,8 @@
"@types/chai": "^4.3.10", "@types/chai": "^4.3.10",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.59.7", "@typescript-eslint/parser": "^8.26.1",
"chai": "4.3.10", "chai": "4.3.10",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@@ -43,5 +43,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -7,7 +7,7 @@ import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, util
import { IAccessService } from "../access/index.js"; import { IAccessService } from "../access/index.js";
import { RegistryItem } from "../registry/index.js"; import { RegistryItem } from "../registry/index.js";
import { Decorator } from "../decorator/index.js"; import { Decorator } from "../decorator/index.js";
import { ICnameProxyService, IEmailService, IPluginConfigService, IUrlService } from "../service/index.js"; import { ICnameProxyService, IEmailService, IPluginConfigService, IServiceGetter, IUrlService } from "../service/index.js";
import { FileStore } from "./file-store.js"; import { FileStore } from "./file-store.js";
import { cloneDeep, forEach, merge } from "lodash-es"; import { cloneDeep, forEach, merge } from "lodash-es";
import { INotificationService } from "../notification/index.js"; import { INotificationService } from "../notification/index.js";
@@ -33,6 +33,7 @@ export type ExecutorOptions = {
user: UserInfo; user: UserInfo;
baseURL?: string; baseURL?: string;
sysInfo?: SysInfo; sysInfo?: SysInfo;
serviceGetter: IServiceGetter;
}; };
export class Executor { export class Executor {
@@ -365,6 +366,7 @@ export class Executor {
step, step,
pipeline: this.pipeline, pipeline: this.pipeline,
}), }),
serviceGetter: this.options.serviceGetter,
}; };
instance.setCtx(taskCtx); instance.setCtx(taskCtx);

View File

@@ -11,11 +11,13 @@ function attachProperty(target: any, propertyKey: string | symbol) {
} }
function getClassProperties(target: any) { function getClassProperties(target: any) {
//获取父类 //获取父类, 向上追溯三层
const parent = Object.getPrototypeOf(target); const parent = Object.getPrototypeOf(target);
const pParent = Object.getPrototypeOf(parent);
const pParentMap = propertyMap[pParent] || {};
const parentMap = propertyMap[parent] || {}; const parentMap = propertyMap[parent] || {};
const current = propertyMap[target] || {}; const current = propertyMap[target] || {};
return _.merge({}, parentMap, current); return _.merge({}, pParentMap, parentMap, current);
} }
function target(target: any, propertyKey?: string | symbol) { function target(target: any, propertyKey?: string | symbol) {

View File

@@ -119,10 +119,12 @@ export abstract class BaseNotification implements INotification {
} }
async onTestRequest() { async onTestRequest() {
await this.doSend({ return await this.doSend({
userId: 0, userId: 0,
title: "【Certd】测试通知【*.foo.com】标题长度测试、测试、测试", title: "【Certd】测试通知【*.foo.com】标题长度测试、测试、测试",
content: "测试通知,*.foo.com", content: `测试通知,*.foo.com
换行测试
`,
pipeline: { pipeline: {
id: 1, id: 1,
title: "证书申请成功【测试流水线】", title: "证书申请成功【测试流水线】",

View File

@@ -2,7 +2,7 @@ import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js"; import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store.js"; import { FileStore } from "../core/file-store.js";
import { IAccessService } from "../access/index.js"; import { IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IUrlService } from "../service/index.js"; import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js"; import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic"; import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
import { HttpClient } from "@certd/basic"; import { HttpClient } from "@certd/basic";
@@ -55,6 +55,14 @@ export type PluginDefine = Registrable & {
[key: string]: any; [key: string]: any;
}; };
shortcut?: {
[key: string]: {
title: string;
icon: string;
action: string;
form: any;
};
};
needPlus?: boolean; needPlus?: boolean;
}; };
@@ -114,6 +122,9 @@ export type TaskInstanceContext = {
user: UserInfo; user: UserInfo;
emitter: TaskEmitter; emitter: TaskEmitter;
//service 容器
serviceGetter?: IServiceGetter;
}; };
export abstract class AbstractTaskPlugin implements ITaskPlugin { export abstract class AbstractTaskPlugin implements ITaskPlugin {
@@ -221,7 +232,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
getStepFromPipeline(stepId: string) { getStepFromPipeline(stepId: string) {
let found: any = null; let found: any = null;
RunnableCollection.each(this.ctx.pipeline.stages, (step) => { RunnableCollection.each(this.ctx.pipeline.stages, step => {
if (step.id === stepId) { if (step.id === stepId) {
found = step; found = step;
return; return;

View File

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

View File

@@ -17,7 +17,6 @@
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off"
"max-len": [0, 160, 2, { "ignoreUrls": true }]
} }
} }

View File

@@ -1,3 +1,7 @@
{ {
"printWidth": 160 "printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
} }

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/lib-huawei **Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-huawei", "name": "@certd/lib-huawei",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts", "types": "./dist/d/index.d.ts",
@@ -18,8 +18,10 @@
"rollup": "^3.7.4" "rollup": "^3.7.4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -17,7 +17,6 @@
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off"
"max-len": [0, 160, 2, { "ignoreUrls": true }]
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"printWidth": 160, "printWidth": 220,
"bracketSpacing": true, "bracketSpacing": true,
"singleQuote": true, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/lib-iframe **Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-iframe", "name": "@certd/lib-iframe",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -20,8 +20,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
@@ -30,5 +30,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -1,7 +1,7 @@
{ {
"printWidth": 160, "printWidth": 220,
"bracketSpacing": true, "bracketSpacing": true,
"singleQuote": true, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-k8s
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-k8s
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-k8s
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/lib-k8s
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/lib-k8s **Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-k8s", "name": "@certd/lib-k8s",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -16,13 +16,13 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@kubernetes/client-node": "0.21.0" "@kubernetes/client-node": "0.21.0"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -1,7 +1,7 @@
{ {
"printWidth": 160, "printWidth": 220,
"bracketSpacing": true, "bracketSpacing": true,
"singleQuote": true, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-server
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-server
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-server
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/lib-server
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/lib-server **Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/lib-server", "name": "@certd/lib-server",
"version": "1.31.2", "version": "1.31.6",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -27,10 +27,10 @@
], ],
"license": "AGPL", "license": "AGPL",
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.31.2", "@certd/acme-client": "^1.31.6",
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@certd/pipeline": "^1.31.2", "@certd/pipeline": "^1.31.6",
"@certd/plus-core": "^1.31.2", "@certd/plus-core": "^1.31.6",
"@midwayjs/cache": "~3.14.0", "@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.20.3", "@midwayjs/core": "~3.20.3",
"@midwayjs/i18n": "~3.20.3", "@midwayjs/i18n": "~3.20.3",
@@ -50,8 +50,8 @@
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/node": "^18", "@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -30,7 +30,7 @@ export abstract class BaseService<T> {
async transaction(callback: (entityManager: EntityManager) => Promise<any>) { async transaction(callback: (entityManager: EntityManager) => Promise<any>) {
const dataSource = this.dataSourceManager.getDataSource('default'); const dataSource = this.dataSourceManager.getDataSource('default');
await dataSource.transaction(callback as any); return await dataSource.transaction(callback as any);
} }
/** /**

View File

@@ -1,7 +1,7 @@
{ {
"printWidth": 160, "printWidth": 220,
"bracketSpacing": true, "bracketSpacing": true,
"singleQuote": true, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/midway-flyway-js **Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/midway-flyway-js", "name": "@certd/midway-flyway-js",
"version": "1.31.2", "version": "1.31.6",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -33,8 +33,8 @@
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/node": "^18", "@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -17,7 +17,6 @@
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off"
"max-len": [0, 160, 2, { "ignoreUrls": true }]
} }
} }

View File

@@ -1,3 +1,7 @@
{ {
"printWidth": 160 "printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
} }

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/plugin-cert
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/plugin-cert
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复dns.la域名申请失败的bug ([1de8eee](https://github.com/certd/certd/commit/1de8eee6ea8307f3c11626af75303d3cc104bb95))
### Performance Improvements
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Performance Improvements
* 1panel支持 apikey方式授权 ([170b2af](https://github.com/certd/certd/commit/170b2afb0e3b125e4ed057f633fe895b5ac3ac22))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
### Bug Fixes ### Bug Fixes

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-cert", "name": "@certd/plugin-cert",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -15,10 +15,10 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.31.2", "@certd/acme-client": "^1.31.6",
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@certd/pipeline": "^1.31.2", "@certd/pipeline": "^1.31.6",
"@certd/plugin-lib": "^1.31.2", "@certd/plugin-lib": "^1.31.6",
"@google-cloud/publicca": "^1.3.0", "@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@@ -30,8 +30,8 @@
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/psl": "^1.1.3", "@types/psl": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.3.6", "chai": "^4.3.6",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
@@ -41,5 +41,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -60,6 +60,7 @@ type AcmeServiceOptions = {
reverseProxy?: string; reverseProxy?: string;
privateKeyType?: PrivateKeyType; privateKeyType?: PrivateKeyType;
signal?: AbortSignal; signal?: AbortSignal;
maxCheckRetryCount?: number;
}; };
export class AcmeService { export class AcmeService {
@@ -144,7 +145,7 @@ export class AcmeService {
accountKey: conf.key, accountKey: conf.key,
accountUrl: conf.accountUrl, accountUrl: conf.accountUrl,
externalAccountBinding: this.eab, externalAccountBinding: this.eab,
backoffAttempts: 20, backoffAttempts: this.options.maxCheckRetryCount || 20,
backoffMin: 5000, backoffMin: 5000,
backoffMax: 10000, backoffMax: 10000,
urlMapping, urlMapping,
@@ -282,15 +283,7 @@ export class AcmeService {
* @returns {Promise} * @returns {Promise}
*/ */
async challengeRemoveFn( async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: HttpChallengeUploader) {
authz: any,
challenge: any,
keyAuthorization: string,
recordReq: any,
recordRes: any,
dnsProvider?: IDnsProvider,
httpUploader?: HttpChallengeUploader
) {
this.logger.info("执行清理"); this.logger.info("执行清理");
/* http-01 */ /* http-01 */
@@ -387,14 +380,7 @@ export class AcmeService {
): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => { ): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => {
return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers); return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers);
}, },
challengeRemoveFn: async ( challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: IDnsProvider): Promise<any> => {
authz: acme.Authorization,
challenge: Challenge,
keyAuthorization: string,
recordReq: any,
recordRes: any,
dnsProvider: IDnsProvider
): Promise<any> => {
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader); return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader);
}, },
signal: this.options.signal, signal: this.options.signal,

View File

@@ -0,0 +1,188 @@
import { AbstractTaskPlugin, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import dayjs from "dayjs";
import type { CertInfo } from "./acme.js";
import { CertReader } from "./cert-reader.js";
import JSZip from "jszip";
import { CertConverter } from "./convert.js";
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success";
export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
@TaskInput({
title: "域名",
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false,
placeholder: "foo.com / *.foo.com / *.bar.com",
tokenSeparators: [",", " ", "", "、", "|"],
},
rules: [{ type: "domains" }],
required: true,
col: {
span: 24,
},
order: -999,
helper:
"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、输入一个空格之后再输入下一个",
})
domains!: string[];
@TaskInput({
title: "证书加密密码",
component: {
name: "input-password",
vModel: "value",
},
required: false,
order: 100,
helper: "转换成PFX、jks格式证书是否需要加密\njks必须设置密码不传则默认123456\npfx不传则为空密码",
})
pfxPassword!: string;
@TaskInput({
title: "PFX证书转换参数",
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
component: {
name: "a-auto-complete",
vModel: "value",
options: [
{ value: "", label: "兼容 Windows Server 最新" },
{ value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2016" },
{ value: "-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2008" },
],
},
required: false,
order: 100,
helper: "兼容Windows Server各个版本",
})
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
userContext!: IContext;
lastStatus!: Step;
@TaskOutput({
title: "域名证书",
})
cert?: CertInfo;
async onInstance() {
this.userContext = this.ctx.userContext;
this.lastStatus = this.ctx.lastStatus as Step;
await this.onInit();
}
abstract onInit(): Promise<void>;
//必须output之后执行
async emitCertApplySuccess() {
const emitter = this.ctx.emitter;
const value = {
cert: this.cert,
file: this._result.files[0].path,
};
await emitter.emit(EVENT_CERT_APPLY_SUCCESS, value);
}
async output(certReader: CertReader, isNew: boolean) {
const cert: CertInfo = certReader.toCertInfo();
this.cert = cert;
this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf();
if (!this._result.pipelinePrivateVars) {
this._result.pipelinePrivateVars = {};
}
this._result.pipelinePrivateVars.cert = cert;
if (isNew) {
try {
const converter = new CertConverter({ logger: this.logger });
const res = await converter.convert({
cert,
pfxPassword: this.pfxPassword,
pfxArgs: this.pfxArgs,
});
if (cert.pfx == null && res.pfx) {
cert.pfx = res.pfx;
}
if (cert.der == null && res.der) {
cert.der = res.der;
}
if (cert.jks == null && res.jks) {
cert.jks = res.jks;
}
this.logger.info("转换证书格式成功");
} catch (e) {
this.logger.error("转换证书格式失败", e);
}
}
if (isNew) {
const zipFileName = certReader.buildCertFileName("zip", certReader.detail.notBefore);
await this.zipCert(cert, zipFileName);
} else {
this.extendsFiles();
}
}
async zipCert(cert: CertInfo, filename: string) {
const zip = new JSZip();
zip.file("证书.pem", cert.crt);
zip.file("私钥.pem", cert.key);
zip.file("中间证书.pem", cert.ic);
zip.file("cert.crt", cert.crt);
zip.file("cert.key", cert.key);
zip.file("intermediate.crt", cert.ic);
zip.file("origin.crt", cert.oc);
zip.file("one.pem", cert.one);
if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
}
if (cert.der) {
zip.file("cert.der", Buffer.from(cert.der, "base64"));
}
if (cert.jks) {
zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
}
zip.file(
"说明.txt",
`证书文件说明
cert.crt证书文件包含证书链pem格式
cert.key私钥文件pem格式
intermediate.crt中间证书文件pem格式
origin.crt原始证书文件不含证书链pem格式
one.pem 证书和私钥简单合并成一个文件pem格式crt正文+key正文
cert.pfxpfx格式证书文件iis服务器使用
cert.derder格式证书文件
cert.jksjks格式证书文件java服务器使用
`
);
const content = await zip.generateAsync({ type: "nodebuffer" });
this.saveFile(filename, content);
this.logger.info(`已保存文件:${filename}`);
}
formatCert(pem: string) {
pem = pem.replace(/\r/g, "");
pem = pem.replace(/\n\n/g, "\n");
pem = pem.replace(/\n$/g, "");
return pem;
}
formatCerts(cert: { crt: string; key: string; csr: string }) {
const newCert: CertInfo = {
crt: this.formatCert(cert.crt),
key: this.formatCert(cert.key),
csr: this.formatCert(cert.csr),
};
return newCert;
}
}

View File

@@ -1,42 +1,10 @@
import { AbstractTaskPlugin, IContext, NotificationBody, Step, TaskEmitter, TaskInput, TaskOutput } from "@certd/pipeline"; import { NotificationBody, Step, TaskInput } from "@certd/pipeline";
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { CertInfo } from "./acme.js";
import { CertReader } from "./cert-reader.js"; import { CertReader } from "./cert-reader.js";
import JSZip from "jszip";
import { CertConverter } from "./convert.js";
import { pick } from "lodash-es"; import { pick } from "lodash-es";
import { CertApplyBaseConvertPlugin } from "./base-convert.js";
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success"; export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
export async function emitCertApplySuccess(emitter: TaskEmitter, cert: CertReader) {
await emitter.emit(EVENT_CERT_APPLY_SUCCESS, cert);
}
export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
@TaskInput({
title: "域名",
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false,
placeholder: "foo.com / *.foo.com / *.bar.com",
tokenSeparators: [",", " ", "", "、", "|"],
},
rules: [{ type: "domains" }],
required: true,
col: {
span: 24,
},
order: -999,
helper:
"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、输入一个空格之后再输入下一个",
})
domains!: string[];
@TaskInput({ @TaskInput({
title: "邮箱", title: "邮箱",
component: { component: {
@@ -50,36 +18,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
}) })
email!: string; email!: string;
@TaskInput({
title: "证书密码",
component: {
name: "input-password",
vModel: "value",
},
required: false,
order: 100,
helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456\npfx不传则为空密码",
})
pfxPassword!: string;
@TaskInput({
title: "PFX证书转换参数",
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
component: {
name: "a-auto-complete",
vModel: "value",
options: [
{ value: "", label: "兼容 Windows Server 最新" },
{ value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2016" },
{ value: "-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2008" },
],
},
required: false,
order: 100,
helper: "兼容Windows Server各个版本",
})
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
@TaskInput({ @TaskInput({
title: "更新天数", title: "更新天数",
value: 35, value: 35,
@@ -111,14 +49,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
// }) // })
csrInfo!: string; csrInfo!: string;
userContext!: IContext;
lastStatus!: Step;
@TaskOutput({
title: "域名证书",
})
cert?: CertInfo;
async onInstance() { async onInstance() {
this.userContext = this.ctx.userContext; this.userContext = this.ctx.userContext;
this.lastStatus = this.ctx.lastStatus as Step; this.lastStatus = this.ctx.lastStatus as Step;
@@ -139,7 +69,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
if (cert != null) { if (cert != null) {
await this.output(cert, true); await this.output(cert, true);
await emitCertApplySuccess(this.ctx.emitter, cert); await this.emitCertApplySuccess();
//清空后续任务的状态,让后续任务能够重新执行 //清空后续任务的状态,让后续任务能够重新执行
this.clearLastStatus(); this.clearLastStatus();
@@ -151,89 +81,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
} }
} }
async output(certReader: CertReader, isNew: boolean) {
const cert: CertInfo = certReader.toCertInfo();
this.cert = cert;
this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf();
if (!this._result.pipelinePrivateVars) {
this._result.pipelinePrivateVars = {};
}
this._result.pipelinePrivateVars.cert = cert;
if (isNew) {
try {
const converter = new CertConverter({ logger: this.logger });
const res = await converter.convert({
cert,
pfxPassword: this.pfxPassword,
pfxArgs: this.pfxArgs,
});
if (cert.pfx == null && res.pfx) {
cert.pfx = res.pfx;
}
if (cert.der == null && res.der) {
cert.der = res.der;
}
if (cert.jks == null && res.jks) {
cert.jks = res.jks;
}
this.logger.info("转换证书格式成功");
} catch (e) {
this.logger.error("转换证书格式失败", e);
}
}
if (isNew) {
const zipFileName = certReader.buildCertFileName("zip", certReader.detail.notBefore);
await this.zipCert(cert, zipFileName);
} else {
this.extendsFiles();
}
}
async zipCert(cert: CertInfo, filename: string) {
const zip = new JSZip();
zip.file("证书.pem", cert.crt);
zip.file("私钥.pem", cert.key);
zip.file("中间证书.pem", cert.ic);
zip.file("cert.crt", cert.crt);
zip.file("cert.key", cert.key);
zip.file("intermediate.crt", cert.ic);
zip.file("origin.crt", cert.oc);
zip.file("one.pem", cert.one);
if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
}
if (cert.der) {
zip.file("cert.der", Buffer.from(cert.der, "base64"));
}
if (cert.jks) {
zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
}
zip.file(
"说明.txt",
`证书文件说明
cert.crt证书文件包含证书链pem格式
cert.key私钥文件pem格式
intermediate.crt中间证书文件pem格式
origin.crt原始证书文件不含证书链pem格式
one.pem 证书和私钥简单合并成一个文件pem格式crt正文+key正文
cert.pfxpfx格式证书文件iis服务器使用
cert.derder格式证书文件
cert.jksjks格式证书文件java服务器使用
`
);
const content = await zip.generateAsync({ type: "nodebuffer" });
this.saveFile(filename, content);
this.logger.info(`已保存文件:${filename}`);
}
/** /**
* 是否更新证书 * 是否更新证书
*/ */
@@ -279,22 +126,6 @@ cert.jksjks格式证书文件java服务器使用
return null; return null;
} }
formatCert(pem: string) {
pem = pem.replace(/\r/g, "");
pem = pem.replace(/\n\n/g, "\n");
pem = pem.replace(/\n$/g, "");
return pem;
}
formatCerts(cert: { crt: string; key: string; csr: string }) {
const newCert: CertInfo = {
crt: this.formatCert(cert.crt),
key: this.formatCert(cert.key),
csr: this.formatCert(cert.csr),
};
return newCert;
}
async readLastCert(): Promise<CertReader | undefined> { async readLastCert(): Promise<CertReader | undefined> {
const cert = this.lastStatus?.status?.output?.cert; const cert = this.lastStatus?.status?.output?.cert;
if (cert == null) { if (cert == null) {

View File

@@ -23,6 +23,7 @@ export class CertReader {
cert: CertInfo; cert: CertInfo;
detail: CertificateInfo; detail: CertificateInfo;
//毫秒时间戳
expires: number; expires: number;
constructor(certInfo: CertInfo) { constructor(certInfo: CertInfo) {
this.cert = certInfo; this.cert = certInfo;
@@ -39,9 +40,13 @@ export class CertReader {
this.cert.one = this.cert.crt + "\n" + this.cert.key; this.cert.one = this.cert.crt + "\n" + this.cert.key;
} }
const { detail, expires } = this.getCrtDetail(this.cert.crt); try {
this.detail = detail; const { detail, expires } = this.getCrtDetail(this.cert.crt);
this.expires = expires.getTime(); this.detail = detail;
this.expires = expires.getTime();
} catch (e) {
throw new Error("证书解析失败:" + e.message);
}
} }
getIc() { getIc() {

View File

@@ -0,0 +1,158 @@
import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import type { CertInfo } from "../acme.js";
import { CertReader } from "../cert-reader.js";
import { CertApplyBaseConvertPlugin } from "../base-convert.js";
import dayjs from "dayjs";
export { CertReader };
export type { CertInfo };
@IsTaskPlugin({
name: "CertApplyUpload",
icon: "ph:certificate",
title: "证书手动上传",
group: pluginGroups.cert.key,
desc: "在证书仓库手动上传后触发部署证书",
default: {
strategy: {
runStrategy: RunStrategy.AlwaysRun,
},
},
shortcut: {
certUpdate: {
title: "更新证书",
icon: "ion:upload",
action: "onCertUpdate",
form: {
columns: {
crt: {
title: "证书",
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 4,
placeholder: "-----BEGIN CERTIFICATE-----\n...\n...\n-----END CERTIFICATE-----",
},
},
rules: [{ required: true, message: "此项必填" }],
col: { span: 24 },
},
},
key: {
title: "私钥",
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 4,
placeholder: "-----BEGIN PRIVATE KEY-----\n...\n...\n-----END PRIVATE KEY----- ",
},
},
rules: [{ required: true, message: "此项必填" }],
col: { span: 24 },
},
},
},
},
},
},
})
export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin {
@TaskInput({
title: "手动上传证书",
component: {
name: "cert-info-updater",
vModel: "modelValue",
},
helper: "手动上传证书",
order: -9999,
required: true,
mergeScript: `
return {
component:{
on:{
updated(scope){
scope.form.input.domains = scope.$event?.domains
}
}
}
}
`,
})
uploadCert!: CertInfo;
@TaskOutput({
title: "证书MD5",
})
certMd5?: string;
async onInstance() {
this.accessService = this.ctx.accessService;
this.logger = this.ctx.logger;
this.userContext = this.ctx.userContext;
this.lastStatus = this.ctx.lastStatus as Step;
}
async onInit(): Promise<void> {}
async getCertFromStore() {
const certReader = new CertReader(this.uploadCert);
if (!certReader.expires && certReader.expires < new Date().getTime()) {
throw new Error("证书已过期,停止部署,请重新上传证书");
}
return certReader;
}
async execute(): Promise<string | void> {
const certReader = await this.getCertFromStore();
const crtMd5 = this.ctx.utils.hash.md5(certReader.cert.crt);
const leftDays = dayjs(certReader.expires).diff(dayjs(), "day");
this.logger.info(`证书过期时间${dayjs(certReader.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${leftDays}`);
if (!this.ctx.inputChanged) {
this.logger.info("输入参数无变化");
const lastCrtMd5 = this.lastStatus?.status?.output?.certMd5;
this.logger.info("证书MD5", crtMd5);
this.logger.info("上次证书MD5", lastCrtMd5);
if (lastCrtMd5 === crtMd5) {
this.logger.info("证书无变化,跳过");
//输出证书MD5
this.certMd5 = crtMd5;
await this.output(certReader, false);
return "skip";
}
this.logger.info("证书有变化,重新部署");
} else {
this.logger.info("输入参数有变化,重新部署");
}
this.clearLastStatus();
//输出证书MD5
this.certMd5 = crtMd5;
await this.output(certReader, true);
//必须output之后执行
await this.emitCertApplySuccess();
return;
}
async onCertUpdate(data: any) {
const certReader = new CertReader(data);
return {
input: {
uploadCert: {
crt: data.crt,
key: data.key,
},
domains: certReader.getAllDomains(),
},
};
}
}
new CertApplyUploadPlugin();

View File

@@ -66,7 +66,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
], ],
}, },
required: true, required: true,
helper: `DNS直接验证域名是在阿里云、腾讯云、华为云、Cloudflare、NameSilo、西数注册的选它 helper: `DNS直接验证域名是在阿里云、腾讯云、华为云、Cloudflare、NameSilo、西数、dns.la注册的,选它;
CNAME代理验证支持任何注册商注册的域名但第一次需要手动添加CNAME记录 CNAME代理验证支持任何注册商注册的域名但第一次需要手动添加CNAME记录
HTTP文件验证不支持泛域名需要配置网站文件上传`, HTTP文件验证不支持泛域名需要配置网站文件上传`,
}) })
@@ -203,8 +203,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
}, },
maybeNeed: true, maybeNeed: true,
required: false, required: false,
helper: helper: "google服务账号授权与EAB授权选填其中一个[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/)\n服务账号授权需要配置代理或者服务器本身在海外",
"google服务账号授权与EAB授权选填其中一个[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/)\n服务账号授权需要配置代理或者服务器本身在海外",
mergeScript: ` mergeScript: `
return { return {
show: ctx.compute(({form})=>{ show: ctx.compute(({form})=>{
@@ -268,6 +267,17 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
}) })
skipLocalVerify = false; skipLocalVerify = false;
@TaskInput({
title: "检查解析重试次数",
value: 20,
component: {
name: "a-input-number",
vModel: "value",
},
helper: "检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值",
})
maxCheckRetryCount = 20;
acme!: AcmeService; acme!: AcmeService;
eab!: EabAccess; eab!: EabAccess;
@@ -314,6 +324,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
reverseProxy: this.reverseProxy, reverseProxy: this.reverseProxy,
privateKeyType: this.privateKeyType, privateKeyType: this.privateKeyType,
signal: this.ctx.signal, signal: this.ctx.signal,
maxCheckRetryCount: this.maxCheckRetryCount,
// cnameProxyService: this.ctx.cnameProxyService, // cnameProxyService: this.ctx.cnameProxyService,
// dnsProviderCreator: this.createDnsProvider.bind(this), // dnsProviderCreator: this.createDnsProvider.bind(this),
}); });

View File

@@ -1,2 +1,6 @@
export { EVENT_CERT_APPLY_SUCCESS } from "./cert-plugin/base-convert.js";
export * from "./cert-plugin/index.js"; export * from "./cert-plugin/index.js";
export * from "./cert-plugin/lego/index.js"; export * from "./cert-plugin/lego/index.js";
export * from "./cert-plugin/custom/index.js";
export const CertApplyPluginNames = ["CertApply", "CertApplyLego", "CertApplyUpload"];

View File

@@ -17,7 +17,6 @@
"@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off"
"max-len": [0, 160, 2, { "ignoreUrls": true }]
} }
} }

View File

@@ -1,3 +1,7 @@
{ {
"printWidth": 160 "printWidth": 220,
"bracketSpacing": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
} }

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
### Performance Improvements
* 上传到主机支持scp方式 ([05b6159](https://github.com/certd/certd/commit/05b6159802b9e85b6a410361b60b5c28875b48e7))
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/plugin-lib
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Performance Improvements
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Performance Improvements
* 支持部署到天翼云CDN ([82a72e0](https://github.com/certd/certd/commit/82a72e0b497efa043d342ad0e33c083a2de79a05))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/plugin-lib **Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-lib", "name": "@certd/plugin-lib",
"private": false, "private": false,
"version": "1.31.2", "version": "1.31.6",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -16,8 +16,8 @@
}, },
"dependencies": { "dependencies": {
"@alicloud/pop-core": "^1.7.10", "@alicloud/pop-core": "^1.7.10",
"@certd/basic": "^1.31.2", "@certd/basic": "^1.31.6",
"@certd/pipeline": "^1.31.2", "@certd/pipeline": "^1.31.6",
"@kubernetes/client-node": "0.21.0", "@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.21.0", "ali-oss": "^6.21.0",
"basic-ftp": "^5.0.5", "basic-ftp": "^5.0.5",
@@ -37,8 +37,8 @@
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/psl": "^1.1.3", "@types/psl": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.3.6", "chai": "^4.3.6",
"eslint": "^8.24.0", "eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
@@ -48,5 +48,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "2a4d64af9502881e2e553ea86a4479158cfa8918" "gitHead": "92446cb048e1bd7c57f36992d9ba376e9f4e7d3b"
} }

View File

@@ -0,0 +1,31 @@
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
@IsAccess({
name: "ctyun",
title: "天翼云授权",
desc: "",
icon: "ant-design:aliyun-outlined",
})
export class CtyunAccess extends BaseAccess {
@AccessInput({
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
},
helper: "[前往创建天翼云AccessKey](https://iam.ctyun.cn/myAccessKey)",
required: true,
})
accessKeyId = "";
@AccessInput({
title: "securityKey",
component: {
placeholder: "securityKey",
},
required: true,
encrypt: true,
helper: "",
})
securityKey = "";
}
new CtyunAccess();

View File

@@ -0,0 +1 @@
export * from "./access/ctyun-access.js";

View File

@@ -4,3 +4,4 @@ export * from "./common/index.js";
export * from "./ftp/index.js"; export * from "./ftp/index.js";
export * from "./tencent/index.js"; export * from "./tencent/index.js";
export * from "./qiniu/index.js"; export * from "./qiniu/index.js";
export * from "./ctyun/index.js";

View File

@@ -7,6 +7,8 @@ import { SshAccess } from "./ssh-access.js";
import stripAnsi from "strip-ansi"; import stripAnsi from "strip-ansi";
import { SocksClient } from "socks"; import { SocksClient } from "socks";
import { SocksProxy, SocksProxyType } from "socks/typings/common/constants.js"; import { SocksProxy, SocksProxyType } from "socks/typings/common/constants.js";
import fs from "fs";
export type TransportItem = { localPath: string; remotePath: string }; export type TransportItem = { localPath: string; remotePath: string };
export class AsyncSsh2Client { export class AsyncSsh2Client {
@@ -61,7 +63,17 @@ export class AsyncSsh2Client {
this.conn = conn; this.conn = conn;
resolve(this.conn); resolve(this.conn);
}) })
.connect(this.connConf); .connect({
...this.connConf,
algorithms: {
kex: [
"ecdh-sha2-nistp256",
"diffie-hellman-group1-sha1",
"diffie-hellman-group14-sha1", // 示例:添加服务器支持的旧算法
"diffie-hellman-group-exchange-sha256",
],
},
});
} catch (e) { } catch (e) {
reject(e); reject(e);
} }
@@ -255,15 +267,15 @@ export class SshClient {
} }
* @param options * @param options
*/ */
async uploadFiles(options: { connectConf: SshAccess; transports: TransportItem[]; mkdirs: boolean; opts?: { mode?: string } }) { async uploadFiles(options: { connectConf: SshAccess; transports: TransportItem[]; mkdirs: boolean; opts?: { mode?: string }; uploadType?: string }) {
const { connectConf, transports, mkdirs, opts } = options; const { connectConf, transports, mkdirs, opts } = options;
await this._call({ await this._call({
connectConf, connectConf,
callable: async (conn: AsyncSsh2Client) => { callable: async (conn: AsyncSsh2Client) => {
const sftp = await conn.getSftp();
this.logger.info("开始上传"); this.logger.info("开始上传");
for (const transport of transports) { if (mkdirs !== false) {
if (mkdirs !== false) { this.logger.info("初始化父目录");
for (const transport of transports) {
const filePath = path.dirname(transport.remotePath); const filePath = path.dirname(transport.remotePath);
let mkdirCmd = `mkdir -p ${filePath} `; let mkdirCmd = `mkdir -p ${filePath} `;
if (conn.windows) { if (conn.windows) {
@@ -281,13 +293,60 @@ export class SshClient {
} }
await conn.exec(mkdirCmd); await conn.exec(mkdirCmd);
} }
await conn.fastPut({ sftp, ...transport, opts });
} }
if (options.uploadType === "sftp") {
const sftp = await conn.getSftp();
for (const transport of transports) {
await conn.fastPut({ sftp, ...transport, opts });
}
} else {
//scp
for (const transport of transports) {
await this.scpUpload({ conn, ...transport, opts });
}
}
this.logger.info("文件全部上传成功"); this.logger.info("文件全部上传成功");
}, },
}); });
} }
async scpUpload(options: { conn: any; localPath: string; remotePath: string; opts?: { mode?: string } }) {
const { conn, localPath, remotePath } = options;
return new Promise((resolve, reject) => {
// 关键步骤:构造 SCP 命令
try {
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
conn.conn.exec(
`scp -t ${remotePath}`, // -t 表示目标模式
(err, stream) => {
if (err) {
return reject(err);
}
// 准备 SCP 协议头
const fileStats = fs.statSync(localPath);
const fileName = path.basename(localPath);
// SCP 协议格式C[权限] [文件大小] [文件名]\n
stream.write(`C0644 ${fileStats.size} ${fileName}\n`);
// 通过管道传输文件
fs.createReadStream(localPath)
.pipe(stream)
.on("finish", () => {
this.logger.info(`上传文件成功:${localPath} => ${remotePath}`);
resolve(true);
})
.on("error", reject);
}
);
} catch (e) {
reject(e);
}
});
}
async removeFiles(opts: { connectConf: SshAccess; files: string[] }) { async removeFiles(opts: { connectConf: SshAccess; files: string[] }) {
const { connectConf, files } = opts; const { connectConf, files } = opts;
await this._call({ await this._call({

View File

@@ -3,34 +3,34 @@ module.exports = {
env: { env: {
browser: true, browser: true,
node: true, node: true,
es6: true es6: true,
}, },
parser: "vue-eslint-parser", parser: 'vue-eslint-parser',
parserOptions: { parserOptions: {
parser: "@typescript-eslint/parser", parser: '@typescript-eslint/parser',
ecmaVersion: 2020, ecmaVersion: 2020,
sourceType: "module", sourceType: 'module',
jsxPragma: "React", jsxPragma: 'React',
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
tsx: true tsx: true,
} },
}, },
extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "prettier"], extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'prettier'],
rules: { rules: {
//"max-len": [0, 200, 2, { ignoreUrls: true }], //"max-len": [0, 200, 2, { ignoreUrls: true }],
"@typescript-eslint/no-unused-vars": "off", '@typescript-eslint/no-unused-vars': 'off',
"no-unused-vars": "off", 'no-unused-vars': 'off',
"@typescript-eslint/ban-ts-ignore": "off", '@typescript-eslint/ban-ts-ignore': 'off',
"@typescript-eslint/ban-ts-comment": "off", '@typescript-eslint/ban-ts-comment': 'off',
"@typescript-eslint/ban-types": "off", '@typescript-eslint/ban-types': 'off',
"@typescript-eslint/explicit-function-return-type": "off", '@typescript-eslint/explicit-function-return-type': 'off',
"@typescript-eslint/no-explicit-any": "off", '@typescript-eslint/no-explicit-any': 'off',
"@typescript-eslint/no-var-requires": "off", '@typescript-eslint/no-var-requires': 'off',
"@typescript-eslint/no-empty-function": "off", '@typescript-eslint/no-empty-function': 'off',
"@typescript-eslint/no-use-before-define": "off", '@typescript-eslint/no-use-before-define': 'off',
"@typescript-eslint/no-non-null-assertion": "off", '@typescript-eslint/no-non-null-assertion': 'off',
"@typescript-eslint/explicit-module-boundary-types": "off" '@typescript-eslint/explicit-module-boundary-types': 'off',
// "@typescript-eslint/no-unused-vars": [ // "@typescript-eslint/no-unused-vars": [
// "error", // "error",
// { // {
@@ -69,5 +69,5 @@ module.exports = {
// math: "always", // math: "always",
// }, // },
// ], // ],
} },
}; };

View File

@@ -1,5 +1,7 @@
{ {
"printWidth": 220,
"trailingComma": "none", "bracketSpacing": true,
"printWidth": 220 "singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid"
} }

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
### Performance Improvements
* 优化图标 ([c56f48c](https://github.com/certd/certd/commit/c56f48c1e3c54c4e203fafb380d9091d75681b7e))
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
### Bug Fixes
* 修复通知选择器无法选择的bug ([f7b88f9](https://github.com/certd/certd/commit/f7b88f9e3b7d9d9122e4fd2003a20c555bd50c7d))
* 修复证书流水线创建失败的bug ([736fe03](https://github.com/certd/certd/commit/736fe038ebda56648bcc4c12884a700341d2c049))
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复站点监控通知通过webhook发送失败的bug ([9be1ecc](https://github.com/certd/certd/commit/9be1ecc8aab3ea23dd0dc2dab3688f4edb90ef2c))
### Performance Improvements
* 宝塔支持doker站点证书部署 ([589a373](https://github.com/certd/certd/commit/589a373142ef7f50d64d3aa767a90b1f4b64da93))
* 保存调整后的列宽 ([873f2b6](https://github.com/certd/certd/commit/873f2b618b9d7320045baf69d6da83afe48a780f))
* 创建证书流水线时,支持更多参数展开 ([36aa7f8](https://github.com/certd/certd/commit/36aa7f82b078a053a102331b3c6f132fb9d492f9))
* 流水线页面可以鼠标按住左右拖动 ([d85a02f](https://github.com/certd/certd/commit/d85a02feeb3183c5abd6c1ea790d5923a32d7271))
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
* 优化选择任务时手机版展示效果 ([d01004d](https://github.com/certd/certd/commit/d01004d53071a75ac91ee21cc96bde9369f77ff3))
* 站点证书监控支持模糊查询 ([0069c0e](https://github.com/certd/certd/commit/0069c0e3992946a8dd6410f299d4fc974ef0e76b))
* 支持飞书通知 ([b82e1dc](https://github.com/certd/certd/commit/b82e1dcd6217b09a7d7e21cd648bb31de320cadf))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Performance Improvements
* 套餐支持3天7天等选项 ([0d71a8e](https://github.com/certd/certd/commit/0d71a8ee501a0e5bb69decf07e8729026e9d85bf))
* 证书仓库增加有效期显示 ([be87124](https://github.com/certd/certd/commit/be87124ada7a093f281ca29a45c86b4ea4644ead))
* 支持部署到天翼云CDN ([82a72e0](https://github.com/certd/certd/commit/82a72e0b497efa043d342ad0e33c083a2de79a05))
## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12) ## [1.31.2](https://github.com/certd/certd/compare/v1.31.1...v1.31.2) (2025-03-12)
**Note:** Version bump only for package @certd/ui-client **Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-client", "name": "@certd/ui-client",
"version": "1.31.2", "version": "1.31.6",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite --open",
@@ -67,6 +67,7 @@
"lucide-vue-next": "^0.477.0", "lucide-vue-next": "^0.477.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"node-forge": "^1.3.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"pinia": "2.1.7", "pinia": "2.1.7",
@@ -95,8 +96,8 @@
"zod-defaults": "^0.1.3" "zod-defaults": "^0.1.3"
}, },
"devDependencies": { "devDependencies": {
"@certd/lib-iframe": "^1.31.2", "@certd/lib-iframe": "^1.31.6",
"@certd/pipeline": "^1.31.2", "@certd/pipeline": "^1.31.6",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12", "@types/chai": "^4.3.12",

View File

@@ -54,6 +54,72 @@
<div class="content unicode" style="display: block;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xecc0;</span>
<div class="name">plesk_</div>
<div class="code-name">&amp;#xecc0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe741;</span>
<div class="name">易支付-01</div>
<div class="code-name">&amp;#xe741;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe606;</span>
<div class="name">1Panel</div>
<div class="code-name">&amp;#xe606;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe73c;</span>
<div class="name">西部数码</div>
<div class="code-name">&amp;#xe73c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe607;</span>
<div class="name">qnap</div>
<div class="code-name">&amp;#xe607;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9cc;</span>
<div class="name">proxmox</div>
<div class="code-name">&amp;#xe9cc;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe604;</span>
<div class="name">aws</div>
<div class="code-name">&amp;#xe604;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe602;</span>
<div class="name">uni-app</div>
<div class="code-name">&amp;#xe602;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe752;</span>
<div class="name">lucky</div>
<div class="code-name">&amp;#xe752;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe719;</span>
<div class="name">ctyun</div>
<div class="code-name">&amp;#xe719;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe748;</span>
<div class="name">雷池</div>
<div class="code-name">&amp;#xe748;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe610;</span> <span class="icon iconfont">&#xe610;</span>
<div class="name">华为</div> <div class="name">华为</div>
@@ -108,7 +174,7 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.svg?t=1730278432006#iconfont') format('svg'); src: url('iconfont.svg?t=1742822771904#iconfont') format('svg');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -134,6 +200,105 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-plesk"></span>
<div class="name">
plesk_
</div>
<div class="code-name">.icon-plesk
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yizhifu"></span>
<div class="name">
易支付-01
</div>
<div class="code-name">.icon-yizhifu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-onepanel"></span>
<div class="name">
1Panel
</div>
<div class="code-name">.icon-onepanel
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xibushuma"></span>
<div class="name">
西部数码
</div>
<div class="code-name">.icon-xibushuma
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-qnap"></span>
<div class="name">
qnap
</div>
<div class="code-name">.icon-qnap
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-proxmox"></span>
<div class="name">
proxmox
</div>
<div class="code-name">.icon-proxmox
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-aws"></span>
<div class="name">
aws
</div>
<div class="code-name">.icon-aws
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-uniapp"></span>
<div class="name">
uni-app
</div>
<div class="code-name">.icon-uniapp
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-lucky"></span>
<div class="name">
lucky
</div>
<div class="code-name">.icon-lucky
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-ctyun"></span>
<div class="name">
ctyun
</div>
<div class="code-name">.icon-ctyun
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-safeline"></span>
<div class="name">
雷池
</div>
<div class="code-name">.icon-safeline
</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont icon-huawei"></span> <span class="icon iconfont icon-huawei"></span>
<div class="name"> <div class="name">
@@ -215,6 +380,94 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-plesk"></use>
</svg>
<div class="name">plesk_</div>
<div class="code-name">#icon-plesk</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yizhifu"></use>
</svg>
<div class="name">易支付-01</div>
<div class="code-name">#icon-yizhifu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-onepanel"></use>
</svg>
<div class="name">1Panel</div>
<div class="code-name">#icon-onepanel</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xibushuma"></use>
</svg>
<div class="name">西部数码</div>
<div class="code-name">#icon-xibushuma</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qnap"></use>
</svg>
<div class="name">qnap</div>
<div class="code-name">#icon-qnap</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-proxmox"></use>
</svg>
<div class="name">proxmox</div>
<div class="code-name">#icon-proxmox</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-aws"></use>
</svg>
<div class="name">aws</div>
<div class="code-name">#icon-aws</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-uniapp"></use>
</svg>
<div class="name">uni-app</div>
<div class="code-name">#icon-uniapp</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-lucky"></use>
</svg>
<div class="name">lucky</div>
<div class="code-name">#icon-lucky</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-ctyun"></use>
</svg>
<div class="name">ctyun</div>
<div class="code-name">#icon-ctyun</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-safeline"></use>
</svg>
<div class="name">雷池</div>
<div class="code-name">#icon-safeline</div>
</li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-huawei"></use> <use xlink:href="#icon-huawei"></use>

View File

@@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4688792 */ font-family: "iconfont"; /* Project id 4688792 */
src: url('iconfont.svg?t=1730278432006#iconfont') format('svg'); src: url('iconfont.svg?t=1742822771904#iconfont') format('svg');
} }
.iconfont { .iconfont {
@@ -11,6 +11,50 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-plesk:before {
content: "\ecc0";
}
.icon-yizhifu:before {
content: "\e741";
}
.icon-onepanel:before {
content: "\e606";
}
.icon-xibushuma:before {
content: "\e73c";
}
.icon-qnap:before {
content: "\e607";
}
.icon-proxmox:before {
content: "\e9cc";
}
.icon-aws:before {
content: "\e604";
}
.icon-uniapp:before {
content: "\e602";
}
.icon-lucky:before {
content: "\e752";
}
.icon-ctyun:before {
content: "\e719";
}
.icon-safeline:before {
content: "\e748";
}
.icon-huawei:before { .icon-huawei:before {
content: "\e610"; content: "\e610";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,83 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "27272666",
"name": "plesk_",
"font_class": "plesk",
"unicode": "ecc0",
"unicode_decimal": 60608
},
{
"icon_id": "23930871",
"name": "易支付-01",
"font_class": "yizhifu",
"unicode": "e741",
"unicode_decimal": 59201
},
{
"icon_id": "40476533",
"name": "1Panel",
"font_class": "onepanel",
"unicode": "e606",
"unicode_decimal": 58886
},
{
"icon_id": "26435508",
"name": "西部数码",
"font_class": "xibushuma",
"unicode": "e73c",
"unicode_decimal": 59196
},
{
"icon_id": "27487624",
"name": "qnap",
"font_class": "qnap",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "27268231",
"name": "proxmox",
"font_class": "proxmox",
"unicode": "e9cc",
"unicode_decimal": 59852
},
{
"icon_id": "31636255",
"name": "aws",
"font_class": "aws",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "34071209",
"name": "uni-app",
"font_class": "uniapp",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "3467975",
"name": "lucky",
"font_class": "lucky",
"unicode": "e752",
"unicode_decimal": 59218
},
{
"icon_id": "41854563",
"name": "ctyun",
"font_class": "ctyun",
"unicode": "e719",
"unicode_decimal": 59161
},
{
"icon_id": "43757703",
"name": "雷池",
"font_class": "safeline",
"unicode": "e748",
"unicode_decimal": 59208
},
{ {
"icon_id": "24164616", "icon_id": "24164616",
"name": "华为", "name": "华为",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,7 +1,7 @@
<template> <template>
<AConfigProvider :locale="locale" :theme="tokenTheme"> <AConfigProvider :locale="locale" :theme="tokenTheme">
<contextHolder />
<fs-form-provider> <fs-form-provider>
<contextHolder />
<router-view /> <router-view />
</fs-form-provider> </fs-form-provider>
</AConfigProvider> </AConfigProvider>
@@ -21,7 +21,7 @@ import AConfigProvider from "ant-design-vue/es/config-provider";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
defineOptions({ defineOptions({
name: "App" name: "App",
}); });
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
provide("modal", modal); provide("modal", modal);
@@ -59,7 +59,7 @@ const tokenTheme = computed(() => {
return { return {
algorithm, algorithm,
token: tokens token: tokens,
}; };
}); });
//其他初始化 //其他初始化

View File

@@ -0,0 +1,27 @@
<template>
<div class="file-input">
<a-button :type="type" @click="onClick">{{ text }}</a-button> {{ fileName }}
<div class="hidden">
<input ref="fileInputRef" type="file" @change="onFileChange" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits, defineProps } from "vue";
const fileInputRef = ref<HTMLInputElement | null>(null);
const props = defineProps<{
text: string;
type: string;
}>();
const fileName = ref("");
const emit = defineEmits(["change"]);
function onClick() {
fileInputRef.value.click();
}
function onFileChange(e: any) {
fileName.value = e.target.files[0].name;
emit("change", e);
}
</script>

View File

@@ -10,10 +10,14 @@ import Plugins from "./plugins/index";
import LoadingButton from "./loading-button.vue"; import LoadingButton from "./loading-button.vue";
import IconSelect from "./icon-select.vue"; import IconSelect from "./icon-select.vue";
import ExpiresTimeText from "./expires-time-text.vue"; import ExpiresTimeText from "./expires-time-text.vue";
import FileInput from "./file-input.vue";
import PemInput from "./pem-input.vue";
export default { export default {
install(app: any) { install(app: any) {
app.component("PiContainer", PiContainer); app.component("PiContainer", PiContainer);
app.component("TextEditable", TextEditable); app.component("TextEditable", TextEditable);
app.component("FileInput", FileInput);
app.component("PemInput", PemInput);
app.component("CronLight", CronLight); app.component("CronLight", CronLight);
app.component("CronEditor", CronEditor); app.component("CronEditor", CronEditor);
@@ -29,5 +33,5 @@ export default {
app.component("ExpiresTimeText", ExpiresTimeText); app.component("ExpiresTimeText", ExpiresTimeText);
app.use(vip); app.use(vip);
app.use(Plugins); app.use(Plugins);
} },
}; };

View File

@@ -0,0 +1,60 @@
<template>
<div class="pem-input">
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
<a-textarea v-bind="textarea" v-model:value="textRef"></a-textarea>
</div>
</template>
<script setup lang="ts">
import { notification } from "ant-design-vue";
import { ref, watch, defineEmits } from "vue";
import FileInput from "/@/components/file-input.vue";
const props = defineProps<{
modelValue?: string;
textarea?: any;
fileInput?: any;
}>();
const emit = defineEmits(["update:modelValue"]);
const textRef = ref();
function emitValue(value: string) {
emit("update:modelValue", value);
}
function onChange(e: any) {
const file = e.target.files[0];
const size = file.size;
if (size > 100 * 1024) {
notification.error({
message: "文件超过100k请选择正确的证书文件",
});
return;
}
const fileReader = new FileReader();
fileReader.onload = function (e: any) {
const value = e.target.result;
emitValue(value);
};
fileReader.readAsText(file); // 以文本形式读取文件
}
watch(
() => props.modelValue,
value => {
textRef.value = value;
},
{
immediate: true,
}
);
</script>
<style lang="less">
.pem-input {
display: flex;
flex-direction: column;
gap: 5px;
}
</style>

View File

@@ -60,6 +60,9 @@ const doTest = async () => {
} }
); );
message.value = "测试请求成功"; message.value = "测试请求成功";
if (res) {
message.value += `,返回:${JSON.stringify(res)}`;
}
} finally { } finally {
loading.value = false; loading.value = false;
} }

View File

@@ -6,6 +6,7 @@ import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selecto
import DomainsVerifyPlanEditor from "/@/components/plugins/cert/domains-verify-plan-editor/index.vue"; import DomainsVerifyPlanEditor from "/@/components/plugins/cert/domains-verify-plan-editor/index.vue";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue"; import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import InputPassword from "./common/input-password.vue"; import InputPassword from "./common/input-password.vue";
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
import ApiTest from "./common/api-test.vue"; import ApiTest from "./common/api-test.vue";
export * from "./cert/index.js"; export * from "./cert/index.js";
export default { export default {
@@ -14,11 +15,13 @@ export default {
app.component("DnsProviderSelector", DnsProviderSelector); app.component("DnsProviderSelector", DnsProviderSelector);
app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor); app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor);
app.component("AccessSelector", AccessSelector); app.component("AccessSelector", AccessSelector);
app.component("CertInfoUpdater", CertInfoUpdater);
app.component("ApiTest", ApiTest); app.component("ApiTest", ApiTest);
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter); app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
app.component("RemoteSelect", RemoteSelect); app.component("RemoteSelect", RemoteSelect);
app.component("CertDomainsGetter", CertDomainsGetter); app.component("CertDomainsGetter", CertDomainsGetter);
app.component("InputPassword", InputPassword); app.component("InputPassword", InputPassword);
} },
}; };

View File

@@ -24,9 +24,9 @@ export async function doRequest(req: RequestHandleReq, opts: any = {}) {
typeName, typeName,
action, action,
data, data,
input input,
}, },
...opts ...opts,
}); });
return res; return res;
} }

View File

@@ -6,16 +6,48 @@ import { FsExtendsCopyable, FsExtendsEditor, FsExtendsJson, FsExtendsTime, FsExt
import "@fast-crud/fast-extends/dist/style.css"; import "@fast-crud/fast-extends/dist/style.css";
import UiAntdv from "@fast-crud/ui-antdv4"; import UiAntdv from "@fast-crud/ui-antdv4";
import "@fast-crud/ui-antdv4/dist/style.css"; import "@fast-crud/ui-antdv4/dist/style.css";
import { merge } from "lodash-es"; import { debounce, merge } from "lodash-es";
import { useCrudPermission } from "../permission"; import { useCrudPermission } from "../permission";
import { App } from "vue"; import { App } from "vue";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import { usePreferences } from "/@/vben/preferences"; import { usePreferences } from "/@/vben/preferences";
import { LocalStorage } from "/@/utils/util.storage";
class ColumnSizeSaver {
save: (key: string, size: number) => void;
constructor() {
this.save = debounce((key: string, size: number) => {
const saveKey = this.getKey();
let data = LocalStorage.get(saveKey);
if (!data) {
data = {};
}
data[key] = size;
LocalStorage.set(saveKey, data);
});
}
getKey() {
const loc = window.location;
const currentUrl = `${loc.pathname}${loc.search}${loc.hash}`;
return `columnSize-${currentUrl}`;
}
get(key: string) {
const saveKey = this.getKey();
const row = LocalStorage.get(saveKey);
return row?.[key];
}
clear() {
const saveKey = this.getKey();
LocalStorage.remove(saveKey);
}
}
const columnSizeSaver = new ColumnSizeSaver();
function install(app: App, options: any = {}) { function install(app: App, options: any = {}) {
app.use(UiAntdv); app.use(UiAntdv);
//设置日志级别 //设置日志级别
setLogger({ level: "info" }); setLogger({ level: "info" });
app.use(FastCrud, { app.use(FastCrud, {
i18n: options.i18n, i18n: options.i18n,
async dictRequest({ url }: any) { async dictRequest({ url }: any) {
@@ -39,20 +71,21 @@ function install(app: App, options: any = {}) {
mobile: { mobile: {
enabled: true, enabled: true,
props: { props: {
isMobile: isMobile isMobile: isMobile,
} },
} },
} },
}, },
table: { table: {
scroll: { scroll: {
x: 960 x: 960,
}, },
size: "small", size: "small",
pagination: false, pagination: false,
onResizeColumn: (w: number | string, col: any) => { onResizeColumn: (w: number, col: any) => {
if (crudBinding.value?.table?.columnsMap && crudBinding.value?.table?.columnsMap[col.key]) { if (crudBinding.value?.table?.columnsMap && crudBinding.value?.table?.columnsMap[col.key]) {
crudBinding.value.table.columnsMap[col.key].width = w; crudBinding.value.table.columnsMap[col.key].width = w;
columnSizeSaver.save(col.key, w);
} }
}, },
conditionalRender: { conditionalRender: {
@@ -70,13 +103,18 @@ function install(app: App, options: any = {}) {
}, },
render() { render() {
return "-"; return "-";
} },
} },
}, },
toolbar: { toolbar: {
export: { export: {
fileType: "excel" fileType: "excel",
} },
columnsFilter: {
async onReset() {
columnSizeSaver.clear();
},
},
}, },
rowHandle: { rowHandle: {
fixed: "right", fixed: "right",
@@ -84,13 +122,15 @@ function install(app: App, options: any = {}) {
view: { type: "link", text: null, icon: "ion:eye-outline" }, view: { type: "link", text: null, icon: "ion:eye-outline" },
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" }, copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" },
edit: { type: "link", text: null, icon: "ion:create-outline" }, edit: { type: "link", text: null, icon: "ion:create-outline" },
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" } remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" },
}, },
dropdown: { dropdown: {
more: { more: {
type: "link" type: "link",
} },
} },
resizable: true,
width: 200,
}, },
request: { request: {
transformQuery: ({ page, form, sort }: PageQuery): UserPageQuery => { transformQuery: ({ page, form, sort }: PageQuery): UserPageQuery => {
@@ -103,10 +143,10 @@ function install(app: App, options: any = {}) {
return { return {
page: { page: {
limit, limit,
offset offset,
}, },
query: form, query: form,
sort sort,
}; };
}, },
transformRes: ({ res }: TransformResProps): PageRes => { transformRes: ({ res }: TransformResProps): PageRes => {
@@ -116,16 +156,16 @@ function install(app: App, options: any = {}) {
currentPage++; currentPage++;
} }
return { currentPage, pageSize, records: res.records, total: res.total, ...res }; return { currentPage, pageSize, records: res.records, total: res.total, ...res };
} },
}, },
search: { search: {
formItem: { formItem: {
wrapperCol: { wrapperCol: {
style: { style: {
width: "50%" width: "50%",
} },
} },
} },
}, },
form: { form: {
display: "flex", display: "flex",
@@ -133,8 +173,8 @@ function install(app: App, options: any = {}) {
//固定label宽度 //固定label宽度
span: null, span: null,
style: { style: {
width: "145px" width: "145px",
} },
}, },
async afterSubmit({ mode }) { async afterSubmit({ mode }) {
if (mode === "add") { if (mode === "add") {
@@ -144,13 +184,13 @@ function install(app: App, options: any = {}) {
} }
}, },
wrapperCol: { wrapperCol: {
span: null span: null,
}, },
wrapper: { wrapper: {
saveRemind: true saveRemind: true,
// inner: true, // inner: true,
// innerContainerSelector: "main.fs-framework-content" // innerContainerSelector: "main.fs-framework-content"
} },
}, },
columns: { columns: {
//最后一列空白,用于自动伸缩列宽 //最后一列空白,用于自动伸缩列宽
@@ -158,23 +198,23 @@ function install(app: App, options: any = {}) {
title: "", title: "",
type: "text", type: "text",
form: { form: {
show: false show: false,
}, },
column: { column: {
order: 99999, order: 99999,
width: -1, width: -1,
columnSetShow: false, columnSetShow: false,
resizable: false resizable: false,
} },
} },
} },
}; };
// 从 useCrud({permission}) 里获取permission参数去设置各个按钮的权限 // 从 useCrud({permission}) 里获取permission参数去设置各个按钮的权限
const permission = props.context?.permission || null; const permission = props.context?.permission || null;
const crudPermission = useCrudPermission({ permission }); const crudPermission = useCrudPermission({ permission });
return crudPermission.merge(opts); return crudPermission.merge(opts);
} },
}); });
// fast-extends里面的扩展组件均为异步组件只有在使用时才会被加载并不会影响首页加载速度 // fast-extends里面的扩展组件均为异步组件只有在使用时才会被加载并不会影响首页加载速度
@@ -202,19 +242,19 @@ function install(app: App, options: any = {}) {
url: action, url: action,
method: "post", method: "post",
headers: { headers: {
"Content-Type": "multipart/form-data" "Content-Type": "multipart/form-data",
}, },
timeout: 60000, timeout: 60000,
data, data,
onUploadProgress: (p: any) => { onUploadProgress: (p: any) => {
onProgress({ percent: Math.round((p.loaded / p.total) * 100) }); onProgress({ percent: Math.round((p.loaded / p.total) * 100) });
} },
}); });
}, },
successHandle(res: any) { successHandle(res: any) {
return res; return res;
} },
} },
}); });
//安装editor //安装editor
@@ -222,10 +262,10 @@ function install(app: App, options: any = {}) {
//编辑器的公共配置 //编辑器的公共配置
wangEditor: { wangEditor: {
editorConfig: { editorConfig: {
MENU_CONF: {} MENU_CONF: {},
}, },
toolbarConfig: {} toolbarConfig: {},
} },
}); });
app.use(FsExtendsJson); app.use(FsExtendsJson);
app.use(FsExtendsTime); app.use(FsExtendsTime);
@@ -250,8 +290,8 @@ function install(app: App, options: any = {}) {
column: { component: { name: "fs-date-format", format: "YYYY-MM-DD" } }, column: { component: { name: "fs-date-format", format: "YYYY-MM-DD" } },
valueBuilder(context: any) { valueBuilder(context: any) {
console.log("time2,valueBuilder", context); console.log("time2,valueBuilder", context);
} },
} },
}); });
// 此处演示自定义字段合并插件 // 此处演示自定义字段合并插件
@@ -266,32 +306,14 @@ function install(app: App, options: any = {}) {
// 合并column配置 // 合并column配置
merge(columnProps, { merge(columnProps, {
form: { show: false }, form: { show: false },
viewForm: { show: true } viewForm: { show: true },
}); });
} }
return columnProps; return columnProps;
} },
}); });
//默认宽度,支持自动拖动调整列宽 //默认宽度,支持自动拖动调整列宽
registerMergeColumnPlugin({
name: "resize-column-plugin",
order: 2,
handle: (columnProps: ColumnCompositionProps) => {
if (!columnProps.column) {
columnProps.column = {};
}
if (columnProps.column.resizable == null) {
columnProps.column.resizable = true;
if (!columnProps.column.width) {
columnProps.column.width = 200;
}
}
return columnProps;
}
});
registerMergeColumnPlugin({ registerMergeColumnPlugin({
name: "resize-column-plugin", name: "resize-column-plugin",
order: 2, order: 2,
@@ -300,16 +322,19 @@ function install(app: App, options: any = {}) {
columnProps.column = {}; columnProps.column = {};
} }
columnProps.column.resizable = true; columnProps.column.resizable = true;
if (columnProps.column.width == null) { const savedColumnWidth = columnSizeSaver.get(columnProps.key as string);
if (savedColumnWidth) {
columnProps.column.width = savedColumnWidth;
} else if (columnProps.column.width == null) {
columnProps.column.width = 200; columnProps.column.width = 200;
} else if (typeof columnProps.column?.width === "string" && columnProps.column.width.indexOf("px") > -1) { } else if (typeof columnProps.column?.width === "string" && columnProps.column.width.indexOf("px") > -1) {
columnProps.column.width = parseInt(columnProps.column.width.replace("px", "")); columnProps.column.width = parseInt(columnProps.column.width.replace("px", ""));
} }
return columnProps; return columnProps;
} },
}); });
} }
export default { export default {
install install,
}; };

View File

@@ -67,13 +67,24 @@ export const certdResources = [
title: "设置", title: "设置",
name: "MineSetting", name: "MineSetting",
path: "/certd/setting", path: "/certd/setting",
redirect: "/certd/cname/record", redirect: "/certd/access",
meta: { meta: {
icon: "ion:settings-outline", icon: "ion:settings-outline",
auth: true, auth: true,
cache: true cache: true
}, },
children: [ children: [
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{ {
title: "CNAME记录管理", title: "CNAME记录管理",
name: "CnameRecord", name: "CnameRecord",
@@ -94,17 +105,7 @@ export const certdResources = [
auth: true auth: true
} }
}, },
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
cache: true
}
},
{ {
title: "OpenKey", title: "OpenKey",
name: "OpenKey", name: "OpenKey",

View File

@@ -88,4 +88,9 @@ body a{
&:hover{ &:hover{
color: #40a9ff; color: #40a9ff;
} }
}
span.fs-icon-svg{
display: flex;
align-items: center;
} }

View File

@@ -4,6 +4,10 @@ export function createAccessApi(from = "user") {
const apiPrefix = from === "sys" ? "/sys/access" : "/pi/access"; const apiPrefix = from === "sys" ? "/sys/access" : "/pi/access";
return { return {
async GetList(query: any) { async GetList(query: any) {
if (query?.query) {
delete query.query.access;
}
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",
method: "post", method: "post",

View File

@@ -77,7 +77,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
type: "dict-select", type: "dict-select",
dict: AccessTypeDictRef, dict: AccessTypeDictRef,
search: { search: {
show: false show: true
}, },
column: { column: {
width: 200, width: 200,

View File

@@ -7,7 +7,7 @@ export const certInfoApi = {
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",
method: "post", method: "post",
data: query data: query,
}); });
}, },
@@ -15,7 +15,7 @@ export const certInfoApi = {
return await request({ return await request({
url: apiPrefix + "/add", url: apiPrefix + "/add",
method: "post", method: "post",
data: obj data: obj,
}); });
}, },
@@ -23,7 +23,7 @@ export const certInfoApi = {
return await request({ return await request({
url: apiPrefix + "/update", url: apiPrefix + "/update",
method: "post", method: "post",
data: obj data: obj,
}); });
}, },
@@ -31,7 +31,7 @@ export const certInfoApi = {
return await request({ return await request({
url: apiPrefix + "/delete", url: apiPrefix + "/delete",
method: "post", method: "post",
params: { id } params: { id },
}); });
}, },
@@ -39,27 +39,35 @@ export const certInfoApi = {
return await request({ return await request({
url: apiPrefix + "/info", url: apiPrefix + "/info",
method: "post", method: "post",
params: { id } params: { id },
}); });
}, },
async ListAll() { async ListAll() {
return await request({ return await request({
url: apiPrefix + "/all", url: apiPrefix + "/all",
method: "post" method: "post",
}); });
}, },
async Upload(body: { id?: number; cert: { crt: string; key: string } }) { async Upload(body: { id?: number; cert: { crt: string; key: string } }) {
return await request({ return await request({
url: apiPrefix + "/upload", url: apiPrefix + "/upload",
method: "post", method: "post",
data: body data: body,
}); });
}, },
async GetCert(id: number): Promise<CertInfo> { async GetCert(id: number): Promise<CertInfo> {
return await request({ return await request({
url: apiPrefix + "/getCert", url: apiPrefix + "/getCert",
method: "post", method: "post",
params: { id: id } params: { id: id },
}); });
} },
async GetOptionsByIds(ids: number[]): Promise<any[]> {
return await request({
url: apiPrefix + "/getOptionsByIds",
method: "post",
data: { ids },
});
},
}; };

View File

@@ -1,17 +1,15 @@
// @ts-ignore // @ts-ignore
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { certInfoApi } from "./api"; import { certInfoApi } from "./api";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useUserStore } from "/@/store/modules/user";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useModal } from "/@/use/use-modal"; import { useModal } from "/@/use/use-modal";
import * as api from "/@/views/certd/pipeline/api";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue"; import CertView from "/@/views/certd/pipeline/cert-view.vue";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = certInfoApi; const api = certInfoApi;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query); return await api.GetList(query);
@@ -50,32 +48,33 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 800, width: 800,
content: () => { content: () => {
return <CertView cert={cert}></CertView>; return <CertView cert={cert}></CertView>;
} },
}); });
}; };
const { openUploadCreateDialog, openUpdateCertDialog } = useCertUpload();
return { return {
crudOptions: { crudOptions: {
request: { request: {
pageRequest, pageRequest,
addRequest, addRequest,
editRequest, editRequest,
delRequest delRequest,
}, },
form: { form: {
labelCol: { labelCol: {
//固定label宽度 //固定label宽度
span: null, span: null,
style: { style: {
width: "100px" width: "100px",
} },
}, },
col: { col: {
span: 22 span: 22,
}, },
wrapper: { wrapper: {
width: 600 width: 600,
} },
}, },
actionbar: { actionbar: {
show: true, show: true,
@@ -85,62 +84,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "primary", type: "primary",
show: false, show: false,
async click() { async click() {
function createCrudOptions() { await openUploadCreateDialog();
return { },
crudOptions: { },
request: { },
addRequest: async (form: any) => { },
return await api.Upload(form); tabs: {
}, name: "fromType",
editRequest: async (form: any) => { show: true,
return await api.Upload(form);
}
},
columns: {
id: {
title: "ID",
type: "number",
form: {
show: false
}
},
"cert.crt": {
title: "证书",
type: "textarea",
form: {
component: {
rows: 4
},
rules: [{ required: true, message: "此项必填" }]
}
},
"cert.key": {
title: "私钥",
type: "textarea",
form: {
component: {
rows: 4
},
rules: [{ required: true, message: "此项必填" }]
}
}
},
form: {
wrapper: {
title: "上传自定义证书"
}
}
}
};
}
const { crudOptions } = createCrudOptions();
const wrapperRef = await openCrudFormDialog({ crudOptions });
}
}
}
}, },
rowHandle: { rowHandle: {
width: 200, width: 100,
fixed: "right", fixed: "right",
buttons: { buttons: {
view: { show: false }, view: { show: false },
@@ -151,12 +105,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
icon: "ph:certificate", icon: "ph:certificate",
async click({ row }) { async click({ row }) {
await viewCert(row); await viewCert(row);
} },
}, },
copy: { show: false }, copy: { show: false },
edit: { show: false }, edit: { show: false },
remove: { show: false } remove: {
} order: 10,
show: false,
},
download: {
order: 9,
title: "下载证书",
type: "link",
icon: "ant-design:download-outlined",
async click({ row }) {
if (!row.certFile) {
notification.error({ message: "证书还未生成,请先运行流水线" });
return;
}
window.open("/api/monitor/cert/download?id=" + row.id);
},
},
},
}, },
columns: { columns: {
id: { id: {
@@ -164,77 +134,91 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id", key: "id",
type: "number", type: "number",
search: { search: {
show: false show: false,
}, },
column: { column: {
width: 100, width: 100,
editable: { editable: {
disabled: true disabled: true,
} },
}, },
form: { form: {
show: false show: false,
} },
}, },
// domain: { fromType: {
// title: "主域名", title: "来源",
// search: {
// show: true
// },
// type: "text",
// form: {
// show: false
// },
// column: {
// width: 180,
// sorter: true,
// component: {
// name: "fs-values-format"
// }
// }
// },
domains: {
title: "全部域名",
search: { search: {
show: true show: true,
},
type: "dict-select",
dict: dict({
data: [
{ label: "流水线", value: "pipeline" },
{ label: "手动上传", value: "upload" },
],
}),
form: {
show: false,
},
column: {
width: 100,
sorter: true,
component: {
color: "auto",
},
conditionalRender: false,
},
valueBuilder({ value, row, key }) {
if (!value) {
row[key] = "pipeline";
}
},
},
domains: {
title: "域名",
search: {
show: true,
}, },
type: "text", type: "text",
form: { form: {
rules: [{ required: true, message: "请输入域名" }] rules: [{ required: true, message: "请输入域名" }],
}, },
column: { column: {
width: 450, width: 450,
sorter: true, sorter: true,
component: { component: {
name: "fs-values-format", name: "fs-values-format",
color: "auto" color: "auto",
} },
} },
}, },
domainCount: { domainCount: {
title: "域名数量", title: "域名数量",
type: "number", type: "number",
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 120, width: 120,
sorter: true, sorter: true,
show: false show: false,
} },
}, },
expiresTime: { expiresLeft: {
title: "过期时间", title: "有效天数",
search: { search: {
show: false show: false,
}, },
type: "date", type: "date",
form: { form: {
show: false show: false,
}, },
column: { column: {
sorter: true, sorter: true,
cellRender({ value }) { conditionalRender: false,
cellRender({ row }) {
const value = row.expiresTime;
if (!value) { if (!value) {
return "-"; return "-";
} }
@@ -243,41 +227,54 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const color = leftDays < 20 ? "red" : "#389e0d"; const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100; const percent = (leftDays / 90) * 100;
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />; return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />;
} },
} },
},
expiresTime: {
title: "过期时间",
search: {
show: false,
},
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
},
}, },
certProvider: { certProvider: {
title: "证书颁发机构", title: "证书颁发机构",
search: { search: {
show: false show: false,
}, },
type: "text", type: "text",
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 200 width: 200,
} },
}, },
applyTime: { applyTime: {
title: "申请时间", title: "申请时间",
search: { search: {
show: false show: false,
}, },
type: "datetime", type: "datetime",
form: { form: {
show: false show: false,
}, },
column: { column: {
sorter: true sorter: true,
} },
}, },
"pipeline.title": { "pipeline.title": {
title: "关联流水线", title: "关联流水线",
search: { show: false }, search: { show: false },
type: "link", type: "link",
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 350, width: 350,
@@ -286,12 +283,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
on: { on: {
onClick({ row }) { onClick({ row }) {
router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } }); router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } });
} },
} },
} },
} },
} },
} },
} },
}; };
} }

View File

@@ -3,7 +3,7 @@
<template #header> <template #header>
<div class="title"> <div class="title">
证书仓库 证书仓库
<span class="sub">从流水线生成的证书后续将支持手动上传证书并部署</span> <span class="sub">从流水线生成的证书</span>
</div> </div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud> <fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
@@ -11,12 +11,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineComponent, onActivated, onMounted } from "vue"; import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createApi } from "./api";
defineOptions({ defineOptions({
name: "CertStore" name: "CertStore",
}); });
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });

View File

@@ -34,28 +34,35 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const settingsStore = useSettingStore(); const settingsStore = useSettingStore();
const checkStatusDict = dict({
data: [
{ label: "成功", value: "ok", color: "green" },
{ label: "检查中", value: "checking", color: "blue" },
{ label: "异常", value: "error", color: "red" },
],
});
return { return {
crudOptions: { crudOptions: {
request: { request: {
pageRequest, pageRequest,
addRequest, addRequest,
editRequest, editRequest,
delRequest delRequest,
}, },
form: { form: {
labelCol: { labelCol: {
//固定label宽度 //固定label宽度
span: null, span: null,
style: { style: {
width: "100px" width: "100px",
} },
}, },
col: { col: {
span: 22 span: 22,
}, },
wrapper: { wrapper: {
width: 600 width: 600,
} },
}, },
actionbar: { actionbar: {
buttons: { buttons: {
@@ -65,7 +72,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
//非plus //非plus
if (crudBinding.value.data.length >= 1) { if (crudBinding.value.data.length >= 1) {
notification.error({ notification.error({
message: "基础版只能添加一个监控站点,请赞助升级专业版" message: "基础版只能添加一个监控站点,请赞助升级专业版",
}); });
mitter.emit("openVipModal"); mitter.emit("openVipModal");
return; return;
@@ -79,15 +86,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const max = suiteDetail.monitorCount.max; const max = suiteDetail.monitorCount.max;
if (max != -1 && max <= suiteDetail.monitorCount.used) { if (max != -1 && max <= suiteDetail.monitorCount.used) {
notification.error({ notification.error({
message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐` message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐`,
}); });
return; return;
} }
} }
await crudExpose.openAdd({}); await crudExpose.openAdd({});
} },
} },
} },
}, },
rowHandle: { rowHandle: {
fixed: "right", fixed: "right",
@@ -98,18 +105,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "link", type: "link",
text: null, text: null,
tooltip: { tooltip: {
title: "立即检查" title: "立即检查",
}, },
icon: "ion:play-sharp", icon: "ion:play-sharp",
click: async ({ row }) => { click: async ({ row }) => {
await api.DoCheck(row.id); await api.DoCheck(row.id);
await crudExpose.doRefresh(); await crudExpose.doRefresh();
notification.success({ notification.success({
message: "检查完成" message: "检查完成",
}); });
} },
} },
} },
}, },
columns: { columns: {
id: { id: {
@@ -117,77 +124,67 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id", key: "id",
type: "number", type: "number",
search: { search: {
show: false show: false,
}, },
column: { column: {
width: 80, width: 80,
align: "center" align: "center",
}, },
form: { form: {
show: false show: false,
} },
}, },
name: { name: {
title: "站点名称", title: "站点名称",
search: { search: {
show: true show: true,
}, },
type: "text", type: "text",
form: { form: {
rules: [{ required: true, message: "请输入站点名称" }] rules: [{ required: true, message: "请输入站点名称" }],
}, },
column: { column: {
width: 160 width: 160,
} },
}, },
domain: { domain: {
title: "网站域名", title: "网站域名",
search: { search: {
show: true show: true,
}, },
type: "text", type: "text",
form: { form: {
rules: [ rules: [
{ required: true, message: "请输入域名" }, { required: true, message: "请输入域名" },
//@ts-ignore //@ts-ignore
{ type: "domains", message: "请输入正确的域名" } { type: "domains", message: "请输入正确的域名" },
] ],
}, },
column: { column: {
width: 200, width: 230,
sorter: true, sorter: true,
cellRender({ value }) { cellRender({ value, row }) {
const url = `https://${value}:${row.httpsPort}`;
return ( return (
<a-tooltip title={value} placement="left"> <a-tooltip title={value} placement="left">
<fs-copyable modelValue={value}></fs-copyable> <fs-copyable modelValue={value}>
<a target="_blank" href={url}>
{value}:{row.httpsPort}
</a>
</fs-copyable>
</a-tooltip> </a-tooltip>
); );
} },
}
},
httpsPort: {
title: "HTTPS端口",
search: {
show: false
}, },
type: "number",
form: {
value: 443,
rules: [{ required: true, message: "请输入端口" }]
},
column: {
align: "center",
width: 100
}
}, },
certDomains: { certDomains: {
title: "证书域名", title: "证书域名",
search: { search: {
show: false show: true,
}, },
type: "text", type: "text",
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 200, width: 200,
@@ -199,55 +196,56 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{value} {value}
</a-tooltip> </a-tooltip>
); );
} },
} },
}, },
certProvider: { certProvider: {
title: "证书颁发者", title: "颁发机构",
search: { search: {
show: false show: false,
}, },
type: "text", type: "text",
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 200, width: 200,
sorter: true, sorter: true,
cellRender({ value }) { cellRender({ value }) {
return <a-tooltip title={value}>{value}</a-tooltip>; return <a-tooltip title={value}>{value}</a-tooltip>;
} },
} },
}, },
certStatus: { certStatus: {
title: "证书状态", title: "证书状态",
search: { search: {
show: true show: true,
}, },
type: "dict-select", type: "dict-select",
dict: dict({ dict: dict({
data: [ data: [
{ label: "正常", value: "ok", color: "green" }, { label: "正常", value: "ok", color: "green" },
{ label: "过期", value: "expired", color: "red" } { label: "过期", value: "expired", color: "red" },
] ],
}), }),
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 100, width: 100,
sorter: true, sorter: true,
show: false show: true,
} align: "center",
},
}, },
certExpiresTime: { certExpiresTime: {
title: "证书到期时间", title: "证书到期时间",
search: { search: {
show: false show: false,
}, },
type: "date", type: "date",
form: { form: {
show: false show: false,
}, },
column: { column: {
sorter: true, sorter: true,
@@ -260,109 +258,111 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const color = leftDays < 20 ? "red" : "#389e0d"; const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100; const percent = (leftDays / 90) * 100;
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />; return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />;
} },
} },
}, },
lastCheckTime: { lastCheckTime: {
title: "上次检查时间", title: "上次检查时间",
search: { search: {
show: false show: false,
}, },
type: "datetime", type: "datetime",
form: { form: {
show: false show: false,
}, },
column: { column: {
sorter: true, sorter: true,
width: 155 width: 155,
}
},
checkStatus: {
title: "检查状态",
search: {
show: false
}, },
type: "dict-select",
dict: dict({
data: [
{ label: "正常", value: "ok", color: "green" },
{ label: "检查中", value: "checking", color: "blue" },
{ label: "异常", value: "error", color: "red" }
]
}),
form: {
show: false
},
column: {
width: 100,
align: "center",
sorter: true
}
},
error: {
title: "错误信息",
search: {
show: false
},
type: "text",
form: {
show: false
},
column: {
width: 200,
sorter: true,
cellRender({ value }) {
return <a-tooltip title={value}>{value}</a-tooltip>;
}
}
},
pipelineId: {
title: "关联流水线id",
search: {
show: false
},
form: { show: false },
type: "number",
column: {
width: 200,
sorter: true,
show: false
}
},
certInfoId: {
title: "证书id",
search: {
show: false
},
type: "number",
form: { show: false },
column: {
width: 100,
sorter: true,
show: false
}
}, },
disabled: { disabled: {
title: "禁用启用", title: "禁用启用",
search: { search: {
show: false show: false,
}, },
type: "dict-switch", type: "dict-switch",
dict: dict({ dict: dict({
data: [ data: [
{ label: "启用", value: false, color: "green" }, { label: "启用", value: false, color: "green" },
{ label: "禁用", value: true, color: "red" } { label: "禁用", value: true, color: "red" },
] ],
}), }),
form: { form: {
value: false value: false,
}, },
column: { column: {
width: 90, width: 100,
sorter: true sorter: true,
} align: "center",
} },
} },
} checkStatus: {
title: "检查状态",
search: {
show: false,
},
type: "dict-select",
dict: checkStatusDict,
form: {
show: false,
},
column: {
width: 100,
align: "center",
sorter: true,
cellRender({ value, row, key }) {
return (
<a-tooltip title={row.error}>
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
</a-tooltip>
);
},
},
},
// error: {
// title: "错误信息",
// search: {
// show: false
// },
// type: "text",
// form: {
// show: false
// },
// column: {
// width: 200,
// sorter: true,
// cellRender({ value }) {
// return <a-tooltip title={value}>{value}</a-tooltip>;
// }
// }
// },
pipelineId: {
title: "关联流水线id",
search: {
show: false,
},
form: { show: false },
type: "number",
column: {
width: 200,
sorter: true,
show: false,
},
},
certInfoId: {
title: "证书id",
search: {
show: false,
},
type: "number",
form: { show: false },
column: {
width: 100,
sorter: true,
show: false,
},
},
},
},
}; };
} }

View File

@@ -4,7 +4,7 @@
<div class="title flex items-center"> <div class="title flex items-center">
站点证书监控 站点证书监控
<div class="sub flex-1"> <div class="sub flex-1">
<div>每天0点检查网站证书的过期时间并发出提醒;</div> <div>每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道;</div>
<div class="flex items-center">基础版限制1条专业版以上无限制当前<vip-button class="ml-5" mode="nav"></vip-button></div> <div class="flex items-center">基础版限制1条专业版以上无限制当前<vip-button class="ml-5" mode="nav"></vip-button></div>
</div> </div>
</div> </div>

View File

@@ -1,16 +1,7 @@
<template> <template>
<div class="notification-selector"> <div class="notification-selector">
<div class="flex-o w-100"> <div class="flex-o w-100">
<fs-dict-select <fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" @update:value="onChange" />
class="flex-1"
:value="modelValue"
:dict="optionsDictRef"
:disabled="disabled"
:render-label="renderLabel"
:slots="selectSlots"
:allow-clear="true"
@update:value="onChange"
/>
<fs-table-select <fs-table-select
ref="tableSelectRef" ref="tableSelectRef"
class="flex-0" class="flex-0"
@@ -21,9 +12,9 @@
search: { show: false }, search: { show: false },
table: { table: {
scroll: { scroll: {
x: 540 x: 540,
} },
} },
}" }"
:show-current="false" :show-current="false"
:show-select="false" :show-select="false"
@@ -50,7 +41,7 @@ import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common"; import { notificationProvide } from "/@/views/certd/notification/common";
defineOptions({ defineOptions({
name: "NotificationSelector" name: "NotificationSelector",
}); });
const props = defineProps<{ const props = defineProps<{
@@ -89,12 +80,12 @@ const optionsDictRef = dict({
{ {
id: 0, id: 0,
name: "使用默认通知", name: "使用默认通知",
icon: "ion:notifications" icon: "ion:notifications",
}, },
...dict.data ...dict.data,
]; ];
dict.setData(data); dict.setData(data);
} },
}); });
const renderLabel = (option: any) => { const renderLabel = (option: any) => {
return <span>{option.name}</span>; return <span>{option.name}</span>;
@@ -115,7 +106,7 @@ const selectSlots = ref({
// res.push(<a-space style="padding: 4px 8px" />); // res.push(<a-space style="padding: 4px 8px" />);
// res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>); // res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
return res; return res;
} },
}); });
const target: Ref<any> = ref({}); const target: Ref<any> = ref({});
@@ -141,13 +132,13 @@ watch(
() => { () => {
return props.modelValue; return props.modelValue;
}, },
async (value) => { async value => {
await optionsDictRef.loadDict(); await optionsDictRef.loadDict();
target.value = optionsDictRef.dataMap[value]; target.value = optionsDictRef.dataMap[value];
emit("selectedChange", target.value); emit("selectedChange", target.value);
}, },
{ {
immediate: true immediate: true,
} }
); );

View File

@@ -6,8 +6,8 @@ const apiPrefix = "/pi/plugin";
const defaultInputDefine = { const defaultInputDefine = {
component: { component: {
name: "a-input", name: "a-input",
vModel: "modelValue" vModel: "modelValue",
} },
}; };
function initPlugins(plugins: any) { function initPlugins(plugins: any) {
@@ -35,7 +35,7 @@ export async function GetList(query: any) {
const plugins = await request({ const plugins = await request({
url: apiPrefix + "/list", url: apiPrefix + "/list",
method: "post", method: "post",
params: query params: query,
}); });
initPlugins(plugins); initPlugins(plugins);
return plugins; return plugins;
@@ -45,7 +45,7 @@ export async function GetGroups(query: any) {
const groups = await request({ const groups = await request({
url: apiPrefix + "/groups", url: apiPrefix + "/groups",
method: "post", method: "post",
params: query params: query,
}); });
const plugins: any = []; const plugins: any = [];
for (const groupKey in groups) { for (const groupKey in groups) {
@@ -60,8 +60,8 @@ export async function GetPluginDefine(type: string) {
url: apiPrefix + "/getDefineByType", url: apiPrefix + "/getDefineByType",
method: "post", method: "post",
data: { data: {
type type,
} },
}); });
initPlugins([define]); initPlugins([define]);
return define; return define;
@@ -71,6 +71,6 @@ export async function GetPluginConfig(req: { id?: number; name: string; type: st
return await request({ return await request({
url: apiPrefix + "/config", url: apiPrefix + "/config",
method: "post", method: "post",
data: req data: req,
}); });
} }

View File

@@ -8,7 +8,7 @@ export async function GetList(query: any) {
return await request({ return await request({
url: apiPrefix + "/page", url: apiPrefix + "/page",
method: "post", method: "post",
data: query data: query,
}); });
} }
@@ -16,7 +16,7 @@ export async function AddObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/add", url: apiPrefix + "/add",
method: "post", method: "post",
data: obj data: obj,
}); });
} }
@@ -24,7 +24,7 @@ export async function UpdateObj(obj: any) {
return await request({ return await request({
url: apiPrefix + "/update", url: apiPrefix + "/update",
method: "post", method: "post",
data: obj data: obj,
}); });
} }
@@ -32,7 +32,7 @@ export async function DelObj(id: any) {
return await request({ return await request({
url: apiPrefix + "/delete", url: apiPrefix + "/delete",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@@ -40,7 +40,7 @@ export async function GetObj(id: any) {
return await request({ return await request({
url: apiPrefix + "/info", url: apiPrefix + "/info",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@@ -48,7 +48,7 @@ export async function GetDetail(id: any) {
return await request({ return await request({
url: apiPrefix + "/detail", url: apiPrefix + "/detail",
method: "post", method: "post",
params: { id } params: { id },
}); });
} }
@@ -56,7 +56,7 @@ export async function Save(pipelineEntity: any) {
return await request({ return await request({
url: apiPrefix + "/save", url: apiPrefix + "/save",
method: "post", method: "post",
data: pipelineEntity data: pipelineEntity,
}); });
} }
@@ -64,7 +64,7 @@ export async function Trigger(id: any, stepId?: string) {
return await request({ return await request({
url: apiPrefix + "/trigger", url: apiPrefix + "/trigger",
method: "post", method: "post",
params: { id, stepId } params: { id, stepId },
}); });
} }
@@ -72,7 +72,7 @@ export async function Cancel(historyId: any) {
return await request({ return await request({
url: apiPrefix + "/cancel", url: apiPrefix + "/cancel",
method: "post", method: "post",
params: { historyId } params: { historyId },
}); });
} }
@@ -80,7 +80,7 @@ export async function BatchUpdateGroup(pipelineIds: number[], groupId: number):
return await request({ return await request({
url: apiPrefix + "/batchUpdateGroup", url: apiPrefix + "/batchUpdateGroup",
method: "post", method: "post",
data: { ids: pipelineIds, groupId } data: { ids: pipelineIds, groupId },
}); });
} }
@@ -88,7 +88,7 @@ export async function BatchDelete(pipelineIds: number[]): Promise<CertInfo> {
return await request({ return await request({
url: apiPrefix + "/batchDelete", url: apiPrefix + "/batchDelete",
method: "post", method: "post",
data: { ids: pipelineIds } data: { ids: pipelineIds },
}); });
} }
@@ -96,14 +96,14 @@ export async function GetFiles(pipelineId: number) {
return await request({ return await request({
url: historyApiPrefix + "/files", url: historyApiPrefix + "/files",
method: "post", method: "post",
params: { pipelineId } params: { pipelineId },
}); });
} }
export async function GetCount() { export async function GetCount() {
return await request({ return await request({
url: apiPrefix + "/count", url: apiPrefix + "/count",
method: "post" method: "post",
}); });
} }
@@ -119,6 +119,6 @@ export async function GetCert(pipelineId: number): Promise<CertInfo> {
return await request({ return await request({
url: certApiPrefix + "/get", url: certApiPrefix + "/get",
method: "post", method: "post",
params: { id: pipelineId } params: { id: pipelineId },
}); });
} }

View File

@@ -0,0 +1,10 @@
import { request } from "/src/api/service";
const apiPrefix = "/monitor/cert";
export async function UploadCert(body: { id?: number; cert: { crt: string; key: string }; pipeline?: any }) {
return await request({
url: apiPrefix + "/upload",
method: "post",
data: body,
});
}

View File

@@ -0,0 +1,58 @@
<template>
<div class="cert-info-updater w-full flex items-center">
<div class="flex-o">
<a-tag>{{ domain }}</a-tag>
<fs-button type="primary" size="small" class="ml-1" icon="ion:upload" text="更新证书" @click="onUploadClick" />
</div>
</div>
</template>
<script lang="tsx" setup>
import { computed, inject } from "vue";
import { useCertUpload } from "./use";
import { getAllDomainsFromCrt } from "/@/views/certd/pipeline/utils";
defineOptions({
name: "CertInfoUpdater",
});
const props = defineProps<{
modelValue?: { crt: string; key: string };
type?: string;
placeholder?: string;
size?: string;
disabled?: boolean;
}>();
const emit = defineEmits(["updated", "update:modelValue"]);
const { openUpdateCertDialog } = useCertUpload();
const domain = computed(() => {
if (!props.modelValue?.crt) {
return "";
}
const domains = getAllDomainsFromCrt(props.modelValue?.crt);
return domains[0];
});
function onUpdated(res: { uploadCert: any }) {
debugger;
emit("update:modelValue", res.uploadCert);
const domains = getAllDomainsFromCrt(res.uploadCert.crt);
emit("updated", { domains });
}
const pipeline: any = inject("pipeline");
function onUploadClick() {
debugger;
openUpdateCertDialog({
onSubmit: onUpdated,
});
}
</script>
<style lang="less">
.cert-info-selector {
width: 100%;
}
</style>

View File

@@ -0,0 +1,237 @@
import { compute, useFormWrapper } from "@fast-crud/fast-crud";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { cloneDeep, omit } from "lodash-es";
import { useReference } from "/@/use/use-refrence";
import { ref } from "vue";
import * as pluginApi from "../api.plugin";
import * as api from "../api";
import { checkPipelineLimit, getAllDomainsFromCrt } from "/@/views/certd/pipeline/utils";
import { useRouter } from "vue-router";
import { nanoid } from "nanoid";
export function useCertUpload() {
const { openCrudFormDialog } = useFormWrapper();
const router = useRouter();
const certInputs = {
"uploadCert.crt": {
title: "证书",
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 4,
placeholder: "-----BEGIN CERTIFICATE-----\n...\n...\n-----END CERTIFICATE-----",
},
},
helper: "选择pem格式证书文件或者粘贴到此",
rules: [{ required: true, message: "此项必填" }],
col: { span: 24 },
order: -9999,
},
},
"uploadCert.key": {
title: "证书私钥",
type: "text",
form: {
component: {
name: "pem-input",
vModel: "modelValue",
textarea: {
rows: 4,
placeholder: "-----BEGIN PRIVATE KEY-----\n...\n...\n-----END PRIVATE KEY----- ",
},
},
helper: "选择pem格式证书私钥文件或者粘贴到此",
rules: [{ required: true, message: "此项必填" }],
col: { span: 24 },
order: -9999,
},
},
};
async function buildUploadCertPluginInputs(getFormData: any) {
const plugin: any = await pluginApi.GetPluginDefine("CertApplyUpload");
const inputs: any = {};
for (const inputKey in plugin.input) {
if (inputKey === "uploadCert" || inputKey === "domains") {
continue;
}
const inputDefine = cloneDeep(plugin.input[inputKey]);
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
show: compute(ctx => {
const form = getFormData();
if (!form) {
return false;
}
let inputDefineShow = true;
if (inputDefine.show != null) {
const computeShow = inputDefine.show as any;
if (computeShow === false) {
inputDefineShow = false;
} else if (computeShow && computeShow.computeFn) {
inputDefineShow = computeShow.computeFn({ form });
}
}
return inputDefineShow;
}),
},
};
}
return inputs;
}
async function openUploadCreateDialog() {
//检查是否流水线数量超出限制
await checkPipelineLimit();
const wrapperRef = ref();
function getFormData() {
if (!wrapperRef.value) {
return null;
}
return wrapperRef.value.getFormData();
}
const inputs = await buildUploadCertPluginInputs(getFormData);
function createCrudOptions() {
return {
crudOptions: {
columns: {
...cloneDeep(certInputs),
...inputs,
notification: {
title: "失败通知",
type: "text",
form: {
value: 0,
component: {
name: NotificationSelector,
vModel: "modelValue",
on: {
selectedChange({ $event, form }: any) {
form.notificationTarget = $event;
},
},
},
order: 101,
helper: "任务执行失败实时提醒",
},
},
},
form: {
wrapper: {
title: "上传证书&创建部署流水线",
saveRemind: false,
},
async doSubmit({ form }: any) {
const cert = form.uploadCert;
const domains = getAllDomainsFromCrt(cert.crt);
const notifications = [];
if (form.notification != null) {
notifications.push({
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: form.notification,
title: form.notificationTarget?.name || "自定义通知",
});
}
const pipelineTitle = domains[0] + "上传证书部署";
const input = omit(form, ["id", "cert", "notification", "notificationTarget"]);
const pipeline = {
title: pipelineTitle,
runnableType: "pipeline",
stages: [
{
id: nanoid(10),
title: "上传证书解析阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
id: nanoid(10),
title: "上传证书解析转换",
runnableType: "task",
steps: [
{
id: nanoid(10),
title: "上传证书解析转换",
runnableType: "step",
input: {
cert: cert,
domains: domains,
...input,
},
strategy: {
runStrategy: 0, // 正常执行
},
type: "CertApplyUpload",
},
],
},
],
},
],
notifications,
};
const id = await api.Save({
title: pipeline.title,
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert_upload",
});
router.push({
path: "/certd/pipeline/detail",
query: { id: id, editMode: "true" },
});
},
},
},
};
}
const { crudOptions } = createCrudOptions();
const wrapper = await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
}
async function openUpdateCertDialog(opts: { onSubmit?: any }) {
function createCrudOptions() {
return {
crudOptions: {
columns: {
...cloneDeep(certInputs),
},
form: {
wrapper: {
title: "手动上传证书",
saveRemind: false,
},
async afterSubmit() {},
async doSubmit({ form }: any) {
if (opts.onSubmit) {
await opts.onSubmit(form);
}
},
},
},
};
}
const { crudOptions } = createCrudOptions();
await openCrudFormDialog({ crudOptions });
}
return {
openUploadCreateDialog,
openUpdateCertDialog,
};
}

View File

@@ -10,22 +10,24 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
const inputs: any = {}; const inputs: any = {};
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const moreParams = [];
for (const plugin of certPlugins) { for (const plugin of certPlugins) {
for (const inputKey in plugin.input) { for (const inputKey in plugin.input) {
if (inputs[inputKey]) { if (inputs[inputKey]) {
inputs[inputKey].form.show = true; // inputs[inputKey].form.show = true;
continue; continue;
} }
const inputDefine = _.cloneDeep(plugin.input[inputKey]); const inputDefine = _.cloneDeep(plugin.input[inputKey]);
if (!inputDefine.required && !inputDefine.maybeNeed) { if (!inputDefine.required && !inputDefine.maybeNeed) {
continue; moreParams.push(inputKey);
// continue;
} }
useReference(inputDefine); useReference(inputDefine);
inputs[inputKey] = { inputs[inputKey] = {
title: inputDefine.title, title: inputDefine.title,
form: { form: {
...inputDefine, ...inputDefine,
show: compute((ctx) => { show: compute(ctx => {
const form = formWrapperRef.value.getFormData(); const form = formWrapperRef.value.getFormData();
if (!form) { if (!form) {
return false; return false;
@@ -34,11 +36,15 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
let inputDefineShow = true; let inputDefineShow = true;
if (inputDefine.show != null) { if (inputDefine.show != null) {
const computeShow = inputDefine.show as any; const computeShow = inputDefine.show as any;
inputDefineShow = computeShow.computeFn({ form }); if (computeShow === false) {
inputDefineShow = false;
} else if (computeShow && computeShow.computeFn) {
inputDefineShow = computeShow.computeFn({ form });
}
} }
return form?.certApplyPlugin === plugin.name && inputDefineShow; return form?.certApplyPlugin === plugin.name && inputDefineShow;
}) }),
} },
}; };
} }
} }
@@ -51,8 +57,17 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
wrapper: { wrapper: {
width: 1350, width: 1350,
saveRemind: false, saveRemind: false,
title: "创建证书流水线" title: "创建证书流水线",
} },
group: {
groups: {
more: {
header: "更多参数",
columns: moreParams,
collapsed: true,
},
},
},
}, },
columns: { columns: {
certApplyPlugin: { certApplyPlugin: {
@@ -61,8 +76,8 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
dict: dict({ dict: dict({
data: [ data: [
{ value: "CertApply", label: "JS-ACME" }, { value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" } { value: "CertApplyLego", label: "Lego-ACME" },
] ],
}), }),
form: { form: {
order: 0, order: 0,
@@ -75,21 +90,21 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
<li>Lego-ACMELego实现DNS提供商LEGO的用户可以使用</li> <li>Lego-ACMELego实现DNS提供商LEGO的用户可以使用</li>
</ul> </ul>
); );
} },
}, },
valueChange: { valueChange: {
handle: async ({ form, value }) => { handle: async ({ form, value }) => {
const config = await api.GetPluginConfig({ const config = await api.GetPluginConfig({
name: value, name: value,
type: "builtIn" type: "builtIn",
}); });
if (config.sysSetting?.input) { if (config.sysSetting?.input) {
merge(form, config.sysSetting.input); merge(form, config.sysSetting.input);
} }
}, },
immediate: true immediate: true,
} },
} },
}, },
...inputs, ...inputs,
triggerCron: { triggerCron: {
@@ -100,11 +115,11 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
component: { component: {
name: "cron-editor", name: "cron-editor",
vModel: "modelValue", vModel: "modelValue",
placeholder: "0 0 4 * * *" placeholder: "0 0 4 * * *",
}, },
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行", helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
order: 100 order: 100,
} },
}, },
notification: { notification: {
title: "失败通知", title: "失败通知",
@@ -117,14 +132,14 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
on: { on: {
selectedChange({ $event, form }) { selectedChange({ $event, form }) {
form.notificationTarget = $event; form.notificationTarget = $event;
} },
} },
}, },
order: 101, order: 101,
helper: "任务执行失败实时提醒" helper: "任务执行失败实时提醒",
} },
} },
} },
} },
}; };
} }

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