mirror of
https://github.com/certd/certd.git
synced 2026-04-05 15:38:11 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b649617e04 | ||
|
|
a4e2287101 | ||
|
|
fbb66f3c43 | ||
|
|
fedf90ea78 | ||
|
|
d558d50102 | ||
|
|
656cb89fe8 | ||
|
|
1e6ddd250e | ||
|
|
1de8eee6ea | ||
|
|
425bba67c5 | ||
|
|
8b0daf7200 | ||
|
|
589a373142 | ||
|
|
0cfc71e4bf | ||
|
|
92dabe6276 | ||
|
|
d1b61b6bf9 | ||
|
|
873f2b618b | ||
|
|
4453070060 | ||
|
|
de40be430b | ||
|
|
29a6a992f0 | ||
|
|
0a7d2d6264 | ||
|
|
e09f92f9ee | ||
|
|
9be1ecc8aa | ||
|
|
729b19c8da | ||
|
|
a9fffa5180 | ||
|
|
0069c0e399 | ||
|
|
b6fd38e293 | ||
|
|
36aa7f82b0 | ||
|
|
d01004d530 | ||
|
|
d85a02feeb | ||
|
|
b82e1dcd62 | ||
|
|
74c6a2266f | ||
|
|
9754223f31 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -3,6 +3,27 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1 +1 @@
|
||||
10:26
|
||||
01:19
|
||||
|
||||
@@ -12,6 +12,9 @@ export default defineConfig({
|
||||
md.use(lightbox, {});
|
||||
}
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://certd.docmirror.cn'
|
||||
},
|
||||
head: [
|
||||
// [
|
||||
// 'meta',
|
||||
@@ -25,9 +28,9 @@ export default defineConfig({
|
||||
name: "keywords",
|
||||
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"}],
|
||||
["link", { rel: "icon", href: "/static/logo/logo.svg" }]
|
||||
],
|
||||
themeConfig: {
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 51 KiB |
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.31.3"
|
||||
"version": "1.31.4"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@lerna-lite/run": "^3.9.3",
|
||||
"@lerna-lite/version": "^3.9.3",
|
||||
"medium-zoom": "^1.1.0",
|
||||
"vitepress": "^1.4.1",
|
||||
"vitepress": "^2.0.0-alpha.4",
|
||||
"vitepress-plugin-lightbox": "^1.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
7
packages/core/acme-client/.prettierrc
Normal file
7
packages/core/acme-client/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"module": "scr/index.js",
|
||||
"main": "src/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.7.2",
|
||||
@@ -67,5 +67,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/core/basic/.prettierrc
Normal file
7
packages/core/basic/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:15
|
||||
02:06
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -44,5 +44,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ function sha256(data: string, digest: BinaryToTextEncoding = 'hex') {
|
||||
return crypto.createHash('sha256').update(data).digest(digest);
|
||||
}
|
||||
|
||||
function HmacSha256(data: string, key: string, digest: BinaryToTextEncoding = 'base64') {
|
||||
return crypto.createHmac('sha256', Buffer.from(key, 'base64')).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) {
|
||||
@@ -18,5 +18,5 @@ export const hashUtils = {
|
||||
md5,
|
||||
sha256,
|
||||
base64,
|
||||
HmacSha256,
|
||||
hmacSha256,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"printWidth": 160
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,8 +16,8 @@
|
||||
"test": "mocha --loader=ts-node/esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/plus-core": "^1.31.3",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@certd/plus-core": "^1.31.4",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
@@ -43,5 +43,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, util
|
||||
import { IAccessService } from "../access/index.js";
|
||||
import { RegistryItem } from "../registry/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 { cloneDeep, forEach, merge } from "lodash-es";
|
||||
import { INotificationService } from "../notification/index.js";
|
||||
@@ -33,6 +33,7 @@ export type ExecutorOptions = {
|
||||
user: UserInfo;
|
||||
baseURL?: string;
|
||||
sysInfo?: SysInfo;
|
||||
serviceGetter: IServiceGetter;
|
||||
};
|
||||
|
||||
export class Executor {
|
||||
@@ -365,6 +366,7 @@ export class Executor {
|
||||
step,
|
||||
pipeline: this.pipeline,
|
||||
}),
|
||||
serviceGetter: this.options.serviceGetter,
|
||||
};
|
||||
instance.setCtx(taskCtx);
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@ function attachProperty(target: any, propertyKey: string | symbol) {
|
||||
}
|
||||
|
||||
function getClassProperties(target: any) {
|
||||
//获取父类
|
||||
//获取父类, 向上追溯三层
|
||||
const parent = Object.getPrototypeOf(target);
|
||||
const pParent = Object.getPrototypeOf(parent);
|
||||
const pParentMap = propertyMap[pParent] || {};
|
||||
const parentMap = propertyMap[parent] || {};
|
||||
const current = propertyMap[target] || {};
|
||||
return _.merge({}, parentMap, current);
|
||||
return _.merge({}, pParentMap, parentMap, current);
|
||||
}
|
||||
|
||||
function target(target: any, propertyKey?: string | symbol) {
|
||||
|
||||
@@ -119,10 +119,12 @@ export abstract class BaseNotification implements INotification {
|
||||
}
|
||||
|
||||
async onTestRequest() {
|
||||
await this.doSend({
|
||||
return await this.doSend({
|
||||
userId: 0,
|
||||
title: "【Certd】测试通知【*.foo.com】,标题长度测试、测试、测试",
|
||||
content: "测试通知,*.foo.com",
|
||||
content: `测试通知,*.foo.com
|
||||
换行测试
|
||||
`,
|
||||
pipeline: {
|
||||
id: 1,
|
||||
title: "证书申请成功【测试流水线】",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Registrable } from "../registry/index.js";
|
||||
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
|
||||
import { FileStore } from "../core/file-store.js";
|
||||
import { IAccessService } from "../access/index.js";
|
||||
import { 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 { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
||||
import { HttpClient } from "@certd/basic";
|
||||
@@ -55,6 +55,14 @@ export type PluginDefine = Registrable & {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
shortcut?: {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
icon: string;
|
||||
action: string;
|
||||
form: any;
|
||||
};
|
||||
};
|
||||
needPlus?: boolean;
|
||||
};
|
||||
|
||||
@@ -114,6 +122,9 @@ export type TaskInstanceContext = {
|
||||
user: UserInfo;
|
||||
|
||||
emitter: TaskEmitter;
|
||||
|
||||
//service 容器
|
||||
serviceGetter?: IServiceGetter;
|
||||
};
|
||||
|
||||
export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
@@ -221,7 +232,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
|
||||
getStepFromPipeline(stepId: string) {
|
||||
let found: any = null;
|
||||
RunnableCollection.each(this.ctx.pipeline.stages, (step) => {
|
||||
RunnableCollection.each(this.ctx.pipeline.stages, step => {
|
||||
if (step.id === stepId) {
|
||||
found = step;
|
||||
return;
|
||||
|
||||
@@ -3,3 +3,6 @@ export * from "./cname.js";
|
||||
export * from "./config.js";
|
||||
export * from "./url.js";
|
||||
export * from "./emit.js";
|
||||
export type IServiceGetter = {
|
||||
get: (name: string) => Promise<any>;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"printWidth": 160
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -23,5 +23,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"printWidth": 160,
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -30,5 +30,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"printWidth": 160,
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,7 +16,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"printWidth": 160,
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -27,10 +27,10 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.31.3",
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/pipeline": "^1.31.3",
|
||||
"@certd/plus-core": "^1.31.3",
|
||||
"@certd/acme-client": "^1.31.4",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@certd/pipeline": "^1.31.4",
|
||||
"@certd/plus-core": "^1.31.4",
|
||||
"@midwayjs/cache": "~3.14.0",
|
||||
"@midwayjs/core": "~3.20.3",
|
||||
"@midwayjs/i18n": "~3.20.3",
|
||||
@@ -61,5 +61,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export abstract class BaseService<T> {
|
||||
|
||||
async transaction(callback: (entityManager: EntityManager) => Promise<any>) {
|
||||
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||
await dataSource.transaction(callback as any);
|
||||
return await dataSource.transaction(callback as any);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"printWidth": 160,
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"printWidth": 160
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -15,10 +15,10 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.31.3",
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/pipeline": "^1.31.3",
|
||||
"@certd/plugin-lib": "^1.31.3",
|
||||
"@certd/acme-client": "^1.31.4",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@certd/pipeline": "^1.31.4",
|
||||
"@certd/plugin-lib": "^1.31.4",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ type AcmeServiceOptions = {
|
||||
reverseProxy?: string;
|
||||
privateKeyType?: PrivateKeyType;
|
||||
signal?: AbortSignal;
|
||||
maxCheckRetryCount?: number;
|
||||
};
|
||||
|
||||
export class AcmeService {
|
||||
@@ -144,7 +145,7 @@ export class AcmeService {
|
||||
accountKey: conf.key,
|
||||
accountUrl: conf.accountUrl,
|
||||
externalAccountBinding: this.eab,
|
||||
backoffAttempts: 20,
|
||||
backoffAttempts: this.options.maxCheckRetryCount || 20,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 10000,
|
||||
urlMapping,
|
||||
@@ -282,15 +283,7 @@ export class AcmeService {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async challengeRemoveFn(
|
||||
authz: any,
|
||||
challenge: any,
|
||||
keyAuthorization: string,
|
||||
recordReq: any,
|
||||
recordRes: any,
|
||||
dnsProvider?: IDnsProvider,
|
||||
httpUploader?: HttpChallengeUploader
|
||||
) {
|
||||
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: HttpChallengeUploader) {
|
||||
this.logger.info("执行清理");
|
||||
|
||||
/* http-01 */
|
||||
@@ -387,14 +380,7 @@ export class AcmeService {
|
||||
): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => {
|
||||
return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers);
|
||||
},
|
||||
challengeRemoveFn: async (
|
||||
authz: acme.Authorization,
|
||||
challenge: Challenge,
|
||||
keyAuthorization: string,
|
||||
recordReq: any,
|
||||
recordRes: any,
|
||||
dnsProvider: IDnsProvider
|
||||
): Promise<any> => {
|
||||
challengeRemoveFn: async (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);
|
||||
},
|
||||
signal: this.options.signal,
|
||||
|
||||
@@ -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.pfx:pfx格式证书文件,iis服务器使用
|
||||
cert.der:der格式证书文件
|
||||
cert.jks:jks格式证书文件,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;
|
||||
}
|
||||
}
|
||||
@@ -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 type { CertInfo } from "./acme.js";
|
||||
import { CertReader } from "./cert-reader.js";
|
||||
import JSZip from "jszip";
|
||||
import { CertConverter } from "./convert.js";
|
||||
import { pick } from "lodash-es";
|
||||
import { CertApplyBaseConvertPlugin } from "./base-convert.js";
|
||||
|
||||
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success";
|
||||
|
||||
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[];
|
||||
|
||||
export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
||||
@TaskInput({
|
||||
title: "邮箱",
|
||||
component: {
|
||||
@@ -50,36 +18,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||
})
|
||||
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({
|
||||
title: "更新天数",
|
||||
value: 35,
|
||||
@@ -111,14 +49,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||
// })
|
||||
csrInfo!: string;
|
||||
|
||||
userContext!: IContext;
|
||||
lastStatus!: Step;
|
||||
|
||||
@TaskOutput({
|
||||
title: "域名证书",
|
||||
})
|
||||
cert?: CertInfo;
|
||||
|
||||
async onInstance() {
|
||||
this.userContext = this.ctx.userContext;
|
||||
this.lastStatus = this.ctx.lastStatus as Step;
|
||||
@@ -139,7 +69,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||
if (cert != null) {
|
||||
await this.output(cert, true);
|
||||
|
||||
await emitCertApplySuccess(this.ctx.emitter, cert);
|
||||
await this.emitCertApplySuccess();
|
||||
//清空后续任务的状态,让后续任务能够重新执行
|
||||
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.pfx:pfx格式证书文件,iis服务器使用
|
||||
cert.der:der格式证书文件
|
||||
cert.jks:jks格式证书文件,java服务器使用
|
||||
`
|
||||
);
|
||||
|
||||
const content = await zip.generateAsync({ type: "nodebuffer" });
|
||||
this.saveFile(filename, content);
|
||||
this.logger.info(`已保存文件:${filename}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否更新证书
|
||||
*/
|
||||
@@ -279,22 +126,6 @@ cert.jks:jks格式证书文件,java服务器使用
|
||||
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> {
|
||||
const cert = this.lastStatus?.status?.output?.cert;
|
||||
if (cert == null) {
|
||||
|
||||
@@ -23,6 +23,7 @@ export class CertReader {
|
||||
cert: CertInfo;
|
||||
|
||||
detail: CertificateInfo;
|
||||
//毫秒时间戳
|
||||
expires: number;
|
||||
constructor(certInfo: CertInfo) {
|
||||
this.cert = certInfo;
|
||||
@@ -39,9 +40,13 @@ export class CertReader {
|
||||
this.cert.one = this.cert.crt + "\n" + this.cert.key;
|
||||
}
|
||||
|
||||
const { detail, expires } = this.getCrtDetail(this.cert.crt);
|
||||
this.detail = detail;
|
||||
this.expires = expires.getTime();
|
||||
try {
|
||||
const { detail, expires } = this.getCrtDetail(this.cert.crt);
|
||||
this.detail = detail;
|
||||
this.expires = expires.getTime();
|
||||
} catch (e) {
|
||||
throw new Error("证书解析失败:" + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
getIc() {
|
||||
|
||||
@@ -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();
|
||||
@@ -203,8 +203,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
|
||||
},
|
||||
maybeNeed: true,
|
||||
required: false,
|
||||
helper:
|
||||
"google服务账号授权与EAB授权选填其中一个,[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/)\n服务账号授权需要配置代理或者服务器本身在海外",
|
||||
helper: "google服务账号授权与EAB授权选填其中一个,[服务账号授权获取方法](https://certd.docmirror.cn/guide/use/google/)\n服务账号授权需要配置代理或者服务器本身在海外",
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
@@ -268,6 +267,17 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
|
||||
})
|
||||
skipLocalVerify = false;
|
||||
|
||||
@TaskInput({
|
||||
title: "检查解析重试次数",
|
||||
value: 35,
|
||||
component: {
|
||||
name: "a-input-number",
|
||||
vModel: "value",
|
||||
},
|
||||
helper: "检查域名验证解析记录重试次数,如果你的域名服务商解析生效速度慢,可以适当增加此值",
|
||||
})
|
||||
maxCheckRetryCount = 35;
|
||||
|
||||
acme!: AcmeService;
|
||||
|
||||
eab!: EabAccess;
|
||||
@@ -314,6 +324,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
|
||||
reverseProxy: this.reverseProxy,
|
||||
privateKeyType: this.privateKeyType,
|
||||
signal: this.ctx.signal,
|
||||
maxCheckRetryCount: this.maxCheckRetryCount,
|
||||
// cnameProxyService: this.ctx.cnameProxyService,
|
||||
// dnsProviderCreator: this.createDnsProvider.bind(this),
|
||||
});
|
||||
|
||||
@@ -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/lego/index.js";
|
||||
export * from "./cert-plugin/custom/index.js";
|
||||
export const CertApplyPluginNames = ["CertApply", "CertApplyLego", "CertApplyUpload"];
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"printWidth": 160
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -16,8 +16,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/pipeline": "^1.31.3",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@certd/pipeline": "^1.31.4",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.21.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -48,5 +48,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "98445afd3ec9e89c5a1b2c2b89dd9ea5e725237a"
|
||||
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
|
||||
}
|
||||
|
||||
@@ -61,7 +61,17 @@ export class AsyncSsh2Client {
|
||||
this.conn = 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) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
@@ -3,34 +3,34 @@ module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
es6: true,
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
jsxPragma: "React",
|
||||
sourceType: 'module',
|
||||
jsxPragma: 'React',
|
||||
ecmaFeatures: {
|
||||
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: {
|
||||
//"max-len": [0, 200, 2, { ignoreUrls: true }],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
// "@typescript-eslint/no-unused-vars": [
|
||||
// "error",
|
||||
// {
|
||||
@@ -69,5 +69,5 @@ module.exports = {
|
||||
// math: "always",
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
|
||||
"trailingComma": "none",
|
||||
"printWidth": 220
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -67,6 +67,7 @@
|
||||
"lucide-vue-next": "^0.477.0",
|
||||
"mitt": "^3.0.1",
|
||||
"nanoid": "^4.0.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"pinia": "2.1.7",
|
||||
@@ -95,8 +96,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.31.3",
|
||||
"@certd/pipeline": "^1.31.3",
|
||||
"@certd/lib-iframe": "^1.31.4",
|
||||
"@certd/pipeline": "^1.31.4",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<AConfigProvider :locale="locale" :theme="tokenTheme">
|
||||
<contextHolder />
|
||||
<fs-form-provider>
|
||||
<contextHolder />
|
||||
<router-view />
|
||||
</fs-form-provider>
|
||||
</AConfigProvider>
|
||||
@@ -21,7 +21,7 @@ import AConfigProvider from "ant-design-vue/es/config-provider";
|
||||
import { Modal } from "ant-design-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "App"
|
||||
name: "App",
|
||||
});
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
provide("modal", modal);
|
||||
@@ -59,7 +59,7 @@ const tokenTheme = computed(() => {
|
||||
|
||||
return {
|
||||
algorithm,
|
||||
token: tokens
|
||||
token: tokens,
|
||||
};
|
||||
});
|
||||
//其他初始化
|
||||
|
||||
27
packages/ui/certd-client/src/components/file-input.vue
Normal file
27
packages/ui/certd-client/src/components/file-input.vue
Normal 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>
|
||||
@@ -10,10 +10,14 @@ import Plugins from "./plugins/index";
|
||||
import LoadingButton from "./loading-button.vue";
|
||||
import IconSelect from "./icon-select.vue";
|
||||
import ExpiresTimeText from "./expires-time-text.vue";
|
||||
import FileInput from "./file-input.vue";
|
||||
import PemInput from "./pem-input.vue";
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.component("PiContainer", PiContainer);
|
||||
app.component("TextEditable", TextEditable);
|
||||
app.component("FileInput", FileInput);
|
||||
app.component("PemInput", PemInput);
|
||||
|
||||
app.component("CronLight", CronLight);
|
||||
app.component("CronEditor", CronEditor);
|
||||
@@ -29,5 +33,5 @@ export default {
|
||||
app.component("ExpiresTimeText", ExpiresTimeText);
|
||||
app.use(vip);
|
||||
app.use(Plugins);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
60
packages/ui/certd-client/src/components/pem-input.vue
Normal file
60
packages/ui/certd-client/src/components/pem-input.vue
Normal 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>
|
||||
@@ -60,6 +60,9 @@ const doTest = async () => {
|
||||
}
|
||||
);
|
||||
message.value = "测试请求成功";
|
||||
if (res) {
|
||||
message.value += `,返回:${JSON.stringify(res)}`;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -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 AccessSelector from "/@/views/certd/access/access-selector/index.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";
|
||||
export * from "./cert/index.js";
|
||||
export default {
|
||||
@@ -14,11 +15,13 @@ export default {
|
||||
app.component("DnsProviderSelector", DnsProviderSelector);
|
||||
app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor);
|
||||
app.component("AccessSelector", AccessSelector);
|
||||
app.component("CertInfoUpdater", CertInfoUpdater);
|
||||
|
||||
app.component("ApiTest", ApiTest);
|
||||
|
||||
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
||||
app.component("RemoteSelect", RemoteSelect);
|
||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||
app.component("InputPassword", InputPassword);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,9 +24,9 @@ export async function doRequest(req: RequestHandleReq, opts: any = {}) {
|
||||
typeName,
|
||||
action,
|
||||
data,
|
||||
input
|
||||
input,
|
||||
},
|
||||
...opts
|
||||
...opts,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,48 @@ import { FsExtendsCopyable, FsExtendsEditor, FsExtendsJson, FsExtendsTime, FsExt
|
||||
import "@fast-crud/fast-extends/dist/style.css";
|
||||
import UiAntdv from "@fast-crud/ui-antdv4";
|
||||
import "@fast-crud/ui-antdv4/dist/style.css";
|
||||
import { merge } from "lodash-es";
|
||||
import { debounce, merge } from "lodash-es";
|
||||
import { useCrudPermission } from "../permission";
|
||||
import { App } from "vue";
|
||||
import { notification } from "ant-design-vue";
|
||||
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 = {}) {
|
||||
app.use(UiAntdv);
|
||||
//设置日志级别
|
||||
setLogger({ level: "info" });
|
||||
|
||||
app.use(FastCrud, {
|
||||
i18n: options.i18n,
|
||||
async dictRequest({ url }: any) {
|
||||
@@ -39,20 +71,21 @@ function install(app: App, options: any = {}) {
|
||||
mobile: {
|
||||
enabled: true,
|
||||
props: {
|
||||
isMobile: isMobile
|
||||
}
|
||||
}
|
||||
}
|
||||
isMobile: isMobile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: {
|
||||
x: 960
|
||||
x: 960,
|
||||
},
|
||||
size: "small",
|
||||
pagination: false,
|
||||
onResizeColumn: (w: number | string, col: any) => {
|
||||
onResizeColumn: (w: number, col: any) => {
|
||||
if (crudBinding.value?.table?.columnsMap && crudBinding.value?.table?.columnsMap[col.key]) {
|
||||
crudBinding.value.table.columnsMap[col.key].width = w;
|
||||
columnSizeSaver.save(col.key, w);
|
||||
}
|
||||
},
|
||||
conditionalRender: {
|
||||
@@ -70,13 +103,18 @@ function install(app: App, options: any = {}) {
|
||||
},
|
||||
render() {
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
toolbar: {
|
||||
export: {
|
||||
fileType: "excel"
|
||||
}
|
||||
fileType: "excel",
|
||||
},
|
||||
columnsFilter: {
|
||||
async onReset() {
|
||||
columnSizeSaver.clear();
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
@@ -84,13 +122,15 @@ function install(app: App, options: any = {}) {
|
||||
view: { type: "link", text: null, icon: "ion:eye-outline" },
|
||||
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" },
|
||||
edit: { type: "link", text: null, icon: "ion:create-outline" },
|
||||
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" }
|
||||
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" },
|
||||
},
|
||||
dropdown: {
|
||||
more: {
|
||||
type: "link"
|
||||
}
|
||||
}
|
||||
type: "link",
|
||||
},
|
||||
},
|
||||
resizable: true,
|
||||
width: 200,
|
||||
},
|
||||
request: {
|
||||
transformQuery: ({ page, form, sort }: PageQuery): UserPageQuery => {
|
||||
@@ -103,10 +143,10 @@ function install(app: App, options: any = {}) {
|
||||
return {
|
||||
page: {
|
||||
limit,
|
||||
offset
|
||||
offset,
|
||||
},
|
||||
query: form,
|
||||
sort
|
||||
sort,
|
||||
};
|
||||
},
|
||||
transformRes: ({ res }: TransformResProps): PageRes => {
|
||||
@@ -116,16 +156,16 @@ function install(app: App, options: any = {}) {
|
||||
currentPage++;
|
||||
}
|
||||
return { currentPage, pageSize, records: res.records, total: res.total, ...res };
|
||||
}
|
||||
},
|
||||
},
|
||||
search: {
|
||||
formItem: {
|
||||
wrapperCol: {
|
||||
style: {
|
||||
width: "50%"
|
||||
}
|
||||
}
|
||||
}
|
||||
width: "50%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
display: "flex",
|
||||
@@ -133,8 +173,8 @@ function install(app: App, options: any = {}) {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "145px"
|
||||
}
|
||||
width: "145px",
|
||||
},
|
||||
},
|
||||
async afterSubmit({ mode }) {
|
||||
if (mode === "add") {
|
||||
@@ -144,13 +184,13 @@ function install(app: App, options: any = {}) {
|
||||
}
|
||||
},
|
||||
wrapperCol: {
|
||||
span: null
|
||||
span: null,
|
||||
},
|
||||
wrapper: {
|
||||
saveRemind: true
|
||||
saveRemind: true,
|
||||
// inner: true,
|
||||
// innerContainerSelector: "main.fs-framework-content"
|
||||
}
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
//最后一列空白,用于自动伸缩列宽
|
||||
@@ -158,23 +198,23 @@ function install(app: App, options: any = {}) {
|
||||
title: "",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
order: 99999,
|
||||
width: -1,
|
||||
columnSetShow: false,
|
||||
resizable: false
|
||||
}
|
||||
}
|
||||
}
|
||||
resizable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 从 useCrud({permission}) 里获取permission参数,去设置各个按钮的权限
|
||||
const permission = props.context?.permission || null;
|
||||
const crudPermission = useCrudPermission({ permission });
|
||||
return crudPermission.merge(opts);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// fast-extends里面的扩展组件均为异步组件,只有在使用时才会被加载,并不会影响首页加载速度
|
||||
@@ -202,19 +242,19 @@ function install(app: App, options: any = {}) {
|
||||
url: action,
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
timeout: 60000,
|
||||
data,
|
||||
onUploadProgress: (p: any) => {
|
||||
onProgress({ percent: Math.round((p.loaded / p.total) * 100) });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
successHandle(res: any) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//安装editor
|
||||
@@ -222,10 +262,10 @@ function install(app: App, options: any = {}) {
|
||||
//编辑器的公共配置
|
||||
wangEditor: {
|
||||
editorConfig: {
|
||||
MENU_CONF: {}
|
||||
MENU_CONF: {},
|
||||
},
|
||||
toolbarConfig: {}
|
||||
}
|
||||
toolbarConfig: {},
|
||||
},
|
||||
});
|
||||
app.use(FsExtendsJson);
|
||||
app.use(FsExtendsTime);
|
||||
@@ -250,8 +290,8 @@ function install(app: App, options: any = {}) {
|
||||
column: { component: { name: "fs-date-format", format: "YYYY-MM-DD" } },
|
||||
valueBuilder(context: any) {
|
||||
console.log("time2,valueBuilder", context);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 此处演示自定义字段合并插件
|
||||
@@ -266,32 +306,14 @@ function install(app: App, options: any = {}) {
|
||||
// 合并column配置
|
||||
merge(columnProps, {
|
||||
form: { show: false },
|
||||
viewForm: { show: true }
|
||||
viewForm: { show: true },
|
||||
});
|
||||
}
|
||||
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({
|
||||
name: "resize-column-plugin",
|
||||
order: 2,
|
||||
@@ -300,16 +322,19 @@ function install(app: App, options: any = {}) {
|
||||
columnProps.column = {};
|
||||
}
|
||||
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;
|
||||
} else if (typeof columnProps.column?.width === "string" && columnProps.column.width.indexOf("px") > -1) {
|
||||
columnProps.column.width = parseInt(columnProps.column.width.replace("px", ""));
|
||||
}
|
||||
return columnProps;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
install,
|
||||
};
|
||||
|
||||
@@ -67,13 +67,24 @@ export const certdResources = [
|
||||
title: "设置",
|
||||
name: "MineSetting",
|
||||
path: "/certd/setting",
|
||||
redirect: "/certd/cname/record",
|
||||
redirect: "/certd/access",
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
auth: true,
|
||||
cache: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "授权管理",
|
||||
name: "AccessManager",
|
||||
path: "/certd/access",
|
||||
component: "/certd/access/index.vue",
|
||||
meta: {
|
||||
icon: "ion:disc-outline",
|
||||
auth: true,
|
||||
cache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "CNAME记录管理",
|
||||
name: "CnameRecord",
|
||||
@@ -94,17 +105,7 @@ export const certdResources = [
|
||||
auth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "授权管理",
|
||||
name: "AccessManager",
|
||||
path: "/certd/access",
|
||||
component: "/certd/access/index.vue",
|
||||
meta: {
|
||||
icon: "ion:disc-outline",
|
||||
auth: true,
|
||||
cache: true
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
title: "OpenKey",
|
||||
name: "OpenKey",
|
||||
|
||||
@@ -88,4 +88,9 @@ body a{
|
||||
&:hover{
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
span.fs-icon-svg{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -4,6 +4,10 @@ export function createAccessApi(from = "user") {
|
||||
const apiPrefix = from === "sys" ? "/sys/access" : "/pi/access";
|
||||
return {
|
||||
async GetList(query: any) {
|
||||
if (query?.query) {
|
||||
delete query.query.access;
|
||||
}
|
||||
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
|
||||
@@ -77,7 +77,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
type: "dict-select",
|
||||
dict: AccessTypeDictRef,
|
||||
search: {
|
||||
show: false
|
||||
show: true
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
|
||||
@@ -7,7 +7,7 @@ export const certInfoApi = {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query
|
||||
data: query,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export const certInfoApi = {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export const certInfoApi = {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ export const certInfoApi = {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -39,27 +39,35 @@ export const certInfoApi = {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
async ListAll() {
|
||||
return await request({
|
||||
url: apiPrefix + "/all",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async Upload(body: { id?: number; cert: { crt: string; key: string } }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/upload",
|
||||
method: "post",
|
||||
data: body
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
async GetCert(id: number): Promise<CertInfo> {
|
||||
return await request({
|
||||
url: apiPrefix + "/getCert",
|
||||
method: "post",
|
||||
params: { id: id }
|
||||
params: { id: id },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async GetOptionsByIds(ids: number[]): Promise<any[]> {
|
||||
return await request({
|
||||
url: apiPrefix + "/getOptionsByIds",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
// @ts-ignore
|
||||
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 dayjs from "dayjs";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useModal } from "/@/use/use-modal";
|
||||
import * as api from "/@/views/certd/pipeline/api";
|
||||
import { notification } from "ant-design-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 {
|
||||
const { t } = useI18n();
|
||||
const api = certInfoApi;
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
@@ -50,32 +48,33 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
width: 800,
|
||||
content: () => {
|
||||
return <CertView cert={cert}></CertView>;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { openUploadCreateDialog, openUpdateCertDialog } = useCertUpload();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
show: true,
|
||||
@@ -85,59 +84,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
type: "primary",
|
||||
show: false,
|
||||
async click() {
|
||||
function createCrudOptions() {
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
addRequest: async (form: any) => {
|
||||
return await api.Upload(form);
|
||||
},
|
||||
editRequest: async (form: any) => {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
await openUploadCreateDialog();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
name: "fromType",
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 100,
|
||||
@@ -151,12 +105,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
icon: "ph:certificate",
|
||||
async click({ row }) {
|
||||
await viewCert(row);
|
||||
}
|
||||
},
|
||||
},
|
||||
copy: { 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: {
|
||||
id: {
|
||||
@@ -164,73 +134,85 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
// domain: {
|
||||
// title: "主域名",
|
||||
// search: {
|
||||
// show: true
|
||||
// },
|
||||
// type: "text",
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// column: {
|
||||
// width: 180,
|
||||
// sorter: true,
|
||||
// component: {
|
||||
// name: "fs-values-format"
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
domains: {
|
||||
title: "全部域名",
|
||||
fromType: {
|
||||
title: "来源",
|
||||
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",
|
||||
form: {
|
||||
rules: [{ required: true, message: "请输入域名" }]
|
||||
rules: [{ required: true, message: "请输入域名" }],
|
||||
},
|
||||
column: {
|
||||
width: 450,
|
||||
sorter: true,
|
||||
component: {
|
||||
name: "fs-values-format",
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
domainCount: {
|
||||
title: "域名数量",
|
||||
type: "number",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
sorter: true,
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
expiresLeft: {
|
||||
title: "有效天数",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
@@ -245,54 +227,54 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}天`} />;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
expiresTime: {
|
||||
title: "过期时间",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: "证书颁发机构",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
applyTime: {
|
||||
title: "申请时间",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
"pipeline.title": {
|
||||
title: "关联流水线",
|
||||
search: { show: false },
|
||||
type: "link",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 350,
|
||||
@@ -301,12 +283,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
on: {
|
||||
onClick({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #header>
|
||||
<div class="title">
|
||||
证书仓库
|
||||
<span class="sub">从流水线生成的证书,后续将支持手动上传证书并部署</span>
|
||||
<span class="sub">从流水线生成的证书</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
@@ -11,12 +11,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, onActivated, onMounted } from "vue";
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { createApi } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "CertStore"
|
||||
name: "CertStore",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||
|
||||
|
||||
@@ -34,28 +34,35 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
|
||||
const checkStatusDict = dict({
|
||||
data: [
|
||||
{ label: "成功", value: "ok", color: "green" },
|
||||
{ label: "检查中", value: "checking", color: "blue" },
|
||||
{ label: "异常", value: "error", color: "red" },
|
||||
],
|
||||
});
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
@@ -65,7 +72,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
//非plus
|
||||
if (crudBinding.value.data.length >= 1) {
|
||||
notification.error({
|
||||
message: "基础版只能添加一个监控站点,请赞助升级专业版"
|
||||
message: "基础版只能添加一个监控站点,请赞助升级专业版",
|
||||
});
|
||||
mitter.emit("openVipModal");
|
||||
return;
|
||||
@@ -79,15 +86,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const max = suiteDetail.monitorCount.max;
|
||||
if (max != -1 && max <= suiteDetail.monitorCount.used) {
|
||||
notification.error({
|
||||
message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐`
|
||||
message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
await crudExpose.openAdd({});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
@@ -98,18 +105,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
type: "link",
|
||||
text: null,
|
||||
tooltip: {
|
||||
title: "立即检查"
|
||||
title: "立即检查",
|
||||
},
|
||||
icon: "ion:play-sharp",
|
||||
click: async ({ row }) => {
|
||||
await api.DoCheck(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({
|
||||
message: "检查完成"
|
||||
message: "检查完成",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
@@ -117,77 +124,67 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
align: "center",
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: "站点名称",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [{ required: true, message: "请输入站点名称" }]
|
||||
rules: [{ required: true, message: "请输入站点名称" }],
|
||||
},
|
||||
column: {
|
||||
width: 160
|
||||
}
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: "网站域名",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入域名" },
|
||||
//@ts-ignore
|
||||
{ type: "domains", message: "请输入正确的域名" }
|
||||
]
|
||||
{ type: "domains", message: "请输入正确的域名" },
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
width: 230,
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
cellRender({ value, row }) {
|
||||
const url = `https://${value}:${row.httpsPort}`;
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
httpsPort: {
|
||||
title: "HTTPS端口",
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
},
|
||||
type: "number",
|
||||
form: {
|
||||
value: 443,
|
||||
rules: [{ required: true, message: "请输入端口" }]
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
certDomains: {
|
||||
title: "证书域名",
|
||||
search: {
|
||||
show: false
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
@@ -199,55 +196,56 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
{value}
|
||||
</a-tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: "证书颁发者",
|
||||
title: "颁发机构",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
return <a-tooltip title={value}>{value}</a-tooltip>;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
certStatus: {
|
||||
title: "证书状态",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "正常", value: "ok", color: "green" },
|
||||
{ label: "过期", value: "expired", color: "red" }
|
||||
]
|
||||
{ label: "过期", value: "expired", color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
show: false
|
||||
}
|
||||
show: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
certExpiresTime: {
|
||||
title: "证书到期时间",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
@@ -260,109 +258,111 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}天`} />;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
lastCheckTime: {
|
||||
title: "上次检查时间",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 155
|
||||
}
|
||||
},
|
||||
checkStatus: {
|
||||
title: "检查状态",
|
||||
search: {
|
||||
show: false
|
||||
width: 155,
|
||||
},
|
||||
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: {
|
||||
title: "禁用启用",
|
||||
search: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: false, color: "green" },
|
||||
{ label: "禁用", value: true, color: "red" }
|
||||
]
|
||||
{ label: "禁用", value: true, color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 90,
|
||||
sorter: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 100,
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="title flex items-center">
|
||||
站点证书监控
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
<template>
|
||||
<div class="notification-selector">
|
||||
<div class="flex-o w-100">
|
||||
<fs-dict-select
|
||||
class="flex-1"
|
||||
:value="modelValue"
|
||||
:dict="optionsDictRef"
|
||||
:disabled="disabled"
|
||||
:render-label="renderLabel"
|
||||
:slots="selectSlots"
|
||||
:allow-clear="true"
|
||||
@update:value="onChange"
|
||||
/>
|
||||
<fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" @update:value="onChange" />
|
||||
<fs-table-select
|
||||
ref="tableSelectRef"
|
||||
class="flex-0"
|
||||
@@ -18,12 +9,12 @@
|
||||
:dict="optionsDictRef"
|
||||
:create-crud-options="createCrudOptions"
|
||||
:crud-options-override="{
|
||||
search: { show: false },
|
||||
search: { show: false, initialForm: { fromType: 'upload' } },
|
||||
table: {
|
||||
scroll: {
|
||||
x: 540
|
||||
}
|
||||
}
|
||||
x: 540,
|
||||
},
|
||||
},
|
||||
}"
|
||||
:show-current="false"
|
||||
:show-select="false"
|
||||
@@ -50,7 +41,7 @@ import createCrudOptions from "../crud";
|
||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
||||
|
||||
defineOptions({
|
||||
name: "NotificationSelector"
|
||||
name: "NotificationSelector",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -89,12 +80,12 @@ const optionsDictRef = dict({
|
||||
{
|
||||
id: 0,
|
||||
name: "使用默认通知",
|
||||
icon: "ion:notifications"
|
||||
icon: "ion:notifications",
|
||||
},
|
||||
...dict.data
|
||||
...dict.data,
|
||||
];
|
||||
dict.setData(data);
|
||||
}
|
||||
},
|
||||
});
|
||||
const renderLabel = (option: any) => {
|
||||
return <span>{option.name}</span>;
|
||||
@@ -115,7 +106,7 @@ const selectSlots = ref({
|
||||
// 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>);
|
||||
return res;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const target: Ref<any> = ref({});
|
||||
@@ -141,13 +132,13 @@ watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async (value) => {
|
||||
async value => {
|
||||
await optionsDictRef.loadDict();
|
||||
target.value = optionsDictRef.dataMap[value];
|
||||
emit("selectedChange", target.value);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ const apiPrefix = "/pi/plugin";
|
||||
const defaultInputDefine = {
|
||||
component: {
|
||||
name: "a-input",
|
||||
vModel: "modelValue"
|
||||
}
|
||||
vModel: "modelValue",
|
||||
},
|
||||
};
|
||||
|
||||
function initPlugins(plugins: any) {
|
||||
@@ -35,7 +35,7 @@ export async function GetList(query: any) {
|
||||
const plugins = await request({
|
||||
url: apiPrefix + "/list",
|
||||
method: "post",
|
||||
params: query
|
||||
params: query,
|
||||
});
|
||||
initPlugins(plugins);
|
||||
return plugins;
|
||||
@@ -45,7 +45,7 @@ export async function GetGroups(query: any) {
|
||||
const groups = await request({
|
||||
url: apiPrefix + "/groups",
|
||||
method: "post",
|
||||
params: query
|
||||
params: query,
|
||||
});
|
||||
const plugins: any = [];
|
||||
for (const groupKey in groups) {
|
||||
@@ -60,8 +60,8 @@ export async function GetPluginDefine(type: string) {
|
||||
url: apiPrefix + "/getDefineByType",
|
||||
method: "post",
|
||||
data: {
|
||||
type
|
||||
}
|
||||
type,
|
||||
},
|
||||
});
|
||||
initPlugins([define]);
|
||||
return define;
|
||||
@@ -71,6 +71,6 @@ export async function GetPluginConfig(req: { id?: number; name: string; type: st
|
||||
return await request({
|
||||
url: apiPrefix + "/config",
|
||||
method: "post",
|
||||
data: req
|
||||
data: req,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function AddObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function UpdateObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function DelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function GetObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function GetDetail(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/detail",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function Save(pipelineEntity: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/save",
|
||||
method: "post",
|
||||
data: pipelineEntity
|
||||
data: pipelineEntity,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function Trigger(id: any, stepId?: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/trigger",
|
||||
method: "post",
|
||||
params: { id, stepId }
|
||||
params: { id, stepId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function Cancel(historyId: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/cancel",
|
||||
method: "post",
|
||||
params: { historyId }
|
||||
params: { historyId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function BatchUpdateGroup(pipelineIds: number[], groupId: number):
|
||||
return await request({
|
||||
url: apiPrefix + "/batchUpdateGroup",
|
||||
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({
|
||||
url: apiPrefix + "/batchDelete",
|
||||
method: "post",
|
||||
data: { ids: pipelineIds }
|
||||
data: { ids: pipelineIds },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,14 +96,14 @@ export async function GetFiles(pipelineId: number) {
|
||||
return await request({
|
||||
url: historyApiPrefix + "/files",
|
||||
method: "post",
|
||||
params: { pipelineId }
|
||||
params: { pipelineId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetCount() {
|
||||
return await request({
|
||||
url: apiPrefix + "/count",
|
||||
method: "post"
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,6 +119,6 @@ export async function GetCert(pipelineId: number): Promise<CertInfo> {
|
||||
return await request({
|
||||
url: certApiPrefix + "/get",
|
||||
method: "post",
|
||||
params: { id: pipelineId }
|
||||
params: { id: pipelineId },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -10,22 +10,24 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
const inputs: any = {};
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
const moreParams = [];
|
||||
for (const plugin of certPlugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
inputs[inputKey].form.show = true;
|
||||
// inputs[inputKey].form.show = true;
|
||||
continue;
|
||||
}
|
||||
const inputDefine = _.cloneDeep(plugin.input[inputKey]);
|
||||
if (!inputDefine.required && !inputDefine.maybeNeed) {
|
||||
continue;
|
||||
moreParams.push(inputKey);
|
||||
// continue;
|
||||
}
|
||||
useReference(inputDefine);
|
||||
inputs[inputKey] = {
|
||||
title: inputDefine.title,
|
||||
form: {
|
||||
...inputDefine,
|
||||
show: compute((ctx) => {
|
||||
show: compute(ctx => {
|
||||
const form = formWrapperRef.value.getFormData();
|
||||
if (!form) {
|
||||
return false;
|
||||
@@ -34,11 +36,15 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
let inputDefineShow = true;
|
||||
if (inputDefine.show != null) {
|
||||
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;
|
||||
})
|
||||
}
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -51,8 +57,17 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
wrapper: {
|
||||
width: 1350,
|
||||
saveRemind: false,
|
||||
title: "创建证书流水线"
|
||||
}
|
||||
title: "创建证书流水线",
|
||||
},
|
||||
group: {
|
||||
groups: {
|
||||
more: {
|
||||
header: "更多参数",
|
||||
columns: moreParams,
|
||||
collapsed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
certApplyPlugin: {
|
||||
@@ -61,8 +76,8 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "CertApply", label: "JS-ACME" },
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" }
|
||||
]
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
order: 0,
|
||||
@@ -75,21 +90,21 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
valueChange: {
|
||||
handle: async ({ form, value }) => {
|
||||
const config = await api.GetPluginConfig({
|
||||
name: value,
|
||||
type: "builtIn"
|
||||
type: "builtIn",
|
||||
});
|
||||
if (config.sysSetting?.input) {
|
||||
merge(form, config.sysSetting.input);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...inputs,
|
||||
triggerCron: {
|
||||
@@ -100,11 +115,11 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *"
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||
order: 100
|
||||
}
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: "失败通知",
|
||||
@@ -117,14 +132,14 @@ export default function (certPlugins: any[], formWrapperRef: any): CreateCrudOpt
|
||||
on: {
|
||||
selectedChange({ $event, form }) {
|
||||
form.notificationTarget = $event;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: "任务执行失败实时提醒"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ export const Dicts = {
|
||||
sslProviderDict: dict({
|
||||
data: [
|
||||
{ value: "letsencrypt", label: "Let‘s Encrypt" },
|
||||
{ value: "zerossl", label: "ZeroSSL" }
|
||||
]
|
||||
{ value: "zerossl", label: "ZeroSSL" },
|
||||
],
|
||||
}),
|
||||
challengeTypeDict: dict({ data: [{ value: "dns", label: "DNS校验" }] }),
|
||||
dnsProviderTypeDict: dict({
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict"
|
||||
})
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict",
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -44,8 +44,8 @@ export default {
|
||||
const notificationApi = createNotificationApi();
|
||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}) as any
|
||||
);
|
||||
|
||||
@@ -60,9 +60,9 @@ export default {
|
||||
return {
|
||||
formWrapperRef,
|
||||
open,
|
||||
formWrapperOptions
|
||||
formWrapperOptions,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { checkPipelineLimit, readCertDetail } from "/@/views/certd/pipeline/utils";
|
||||
import { omit } from "lodash-es";
|
||||
import * as api from "/@/views/certd/pipeline/api";
|
||||
import { message } from "ant-design-vue";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export function setRunnableIds(pipeline: any) {
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export function useCertd(certdFormRef: any) {
|
||||
const router = useRouter();
|
||||
async function openAddCertdPipelineDialog() {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
|
||||
certdFormRef.value.open(async ({ form }: any) => {
|
||||
const certDetail = readCertDetail(form.cert.crt);
|
||||
// 添加certd pipeline
|
||||
const triggers = [];
|
||||
if (form.triggerCron) {
|
||||
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
|
||||
}
|
||||
const notifications = [];
|
||||
if (form.notification != null) {
|
||||
notifications.push({
|
||||
type: "custom",
|
||||
when: ["error", "turnToSuccess", "success"],
|
||||
notificationId: form.notification,
|
||||
title: form.notificationTarget?.name || "自定义通知",
|
||||
});
|
||||
}
|
||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin"]);
|
||||
let pipeline = {
|
||||
title: form.domains[0] + "证书自动化",
|
||||
runnableType: "pipeline",
|
||||
stages: [
|
||||
{
|
||||
title: "证书申请阶段",
|
||||
maxTaskCount: 1,
|
||||
runnableType: "stage",
|
||||
tasks: [
|
||||
{
|
||||
title: "证书申请任务",
|
||||
runnableType: "task",
|
||||
steps: [
|
||||
{
|
||||
title: "申请证书",
|
||||
runnableType: "step",
|
||||
input: {
|
||||
renewDays: 35,
|
||||
...pluginInput,
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0, // 正常执行
|
||||
},
|
||||
type: form.certApplyPlugin,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
triggers,
|
||||
notifications,
|
||||
};
|
||||
pipeline = setRunnableIds(pipeline);
|
||||
|
||||
/**
|
||||
* // cert: 证书; backup: 备份; custom:自定义;
|
||||
* type: string;
|
||||
* // custom: 自定义; monitor: 监控;
|
||||
* from: string;
|
||||
*/
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
});
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
openAddCertdPipelineDialog,
|
||||
};
|
||||
}
|
||||
@@ -4,62 +4,26 @@ import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
|
||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||
import { nanoid } from "nanoid";
|
||||
import { message, Modal, notification } from "ant-design-vue";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { env } from "/@/utils/util.env";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import dayjs from "dayjs";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import * as _ from "lodash-es";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useModal } from "/@/use/use-modal";
|
||||
import CertView from "./cert-view.vue";
|
||||
import { eachStages } from "./utils";
|
||||
import { createNotificationApi as createNotificationApi } from "../notification/api";
|
||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||
import { setRunnableIds, useCertd } from "/@/views/certd/pipeline/certd-form/use";
|
||||
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
||||
|
||||
export default function ({ crudExpose, context: { certdFormRef, groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const lastResRef = ref();
|
||||
|
||||
function setRunnableIds(pipeline: any) {
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { openAddCertdPipelineDialog } = useCertd(certdFormRef);
|
||||
const { openUploadCreateDialog } = useCertUpload();
|
||||
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
@@ -76,7 +40,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
if (form.content == null) {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
title: form.title,
|
||||
});
|
||||
} else {
|
||||
//复制的流水线
|
||||
@@ -96,90 +60,6 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
return res;
|
||||
};
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
async function addCertdPipeline() {
|
||||
//检查是否流水线数量超出限制
|
||||
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
|
||||
//检查数量是否超限
|
||||
|
||||
const suiteDetail = await mySuiteApi.SuiteDetailGet();
|
||||
const max = suiteDetail.pipelineCount.max;
|
||||
if (max != -1 && max <= suiteDetail.pipelineCount.used) {
|
||||
notification.error({
|
||||
message: `对不起,您最多只能创建${max}条流水线,请购买或升级套餐`
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
certdFormRef.value.open(async ({ form }: any) => {
|
||||
// 添加certd pipeline
|
||||
const triggers = [];
|
||||
if (form.triggerCron) {
|
||||
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
|
||||
}
|
||||
const notifications = [];
|
||||
if (form.notification != null) {
|
||||
notifications.push({
|
||||
type: "custom",
|
||||
when: ["error", "turnToSuccess", "success"],
|
||||
notificationId: form.notification,
|
||||
title: form.notificationTarget?.name || "自定义通知"
|
||||
});
|
||||
}
|
||||
let pipeline = {
|
||||
title: form.domains[0] + "证书自动化",
|
||||
runnableType: "pipeline",
|
||||
stages: [
|
||||
{
|
||||
title: "证书申请阶段",
|
||||
maxTaskCount: 1,
|
||||
runnableType: "stage",
|
||||
tasks: [
|
||||
{
|
||||
title: "证书申请任务",
|
||||
runnableType: "task",
|
||||
steps: [
|
||||
{
|
||||
title: "申请证书",
|
||||
runnableType: "step",
|
||||
input: {
|
||||
renewDays: 35,
|
||||
...form
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0 // 正常执行
|
||||
},
|
||||
type: form.certApplyPlugin
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
triggers,
|
||||
notifications
|
||||
};
|
||||
pipeline = setRunnableIds(pipeline);
|
||||
|
||||
/**
|
||||
* // cert: 证书; backup: 备份; custom:自定义;
|
||||
* type: string;
|
||||
* // custom: 自定义; monitor: 监控;
|
||||
* from: string;
|
||||
*/
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
from: "custom"
|
||||
});
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
});
|
||||
}
|
||||
|
||||
const model = useModal();
|
||||
const viewCert = async (row: any) => {
|
||||
const cert = await api.GetCert(row.id);
|
||||
@@ -195,7 +75,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
width: 800,
|
||||
content: () => {
|
||||
return <CertView cert={cert}></CertView>;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -230,7 +110,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
<div> {children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
@@ -242,7 +122,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
settings: {
|
||||
plugins: {
|
||||
@@ -259,46 +139,57 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
selectedRowKeys,
|
||||
onSelectedChanged(selected) {
|
||||
console.log("已选择变化:", selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
order: 5,
|
||||
text: "自定义流水线"
|
||||
icon: "ion:ios-add-circle-outline",
|
||||
text: "自定义流水线",
|
||||
},
|
||||
addCertd: {
|
||||
order: 1,
|
||||
text: "创建证书流水线",
|
||||
type: "primary",
|
||||
icon: "ion:ios-add-circle-outline",
|
||||
click() {
|
||||
addCertdPipeline();
|
||||
}
|
||||
}
|
||||
}
|
||||
openAddCertdPipelineDialog();
|
||||
},
|
||||
},
|
||||
uploadCert: {
|
||||
order: 2,
|
||||
text: "上传证书部署",
|
||||
type: "primary",
|
||||
icon: "ion:cloud-upload-outline",
|
||||
click() {
|
||||
openUploadCreateDialog();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
afterSubmit({ form, res, mode }) {
|
||||
if (mode === "add") {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: res.id, editMode: "true" } });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: { x: 1500 }
|
||||
scroll: { x: 1500 },
|
||||
},
|
||||
tabs: {
|
||||
name: "groupId",
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
dropdown: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
buttons: {
|
||||
play: {
|
||||
@@ -313,30 +204,30 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
async onOk() {
|
||||
await api.Trigger(row.id);
|
||||
notification.success({ message: "管道已经开始运行" });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
view: {
|
||||
show: false,
|
||||
click({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||||
}
|
||||
},
|
||||
},
|
||||
copy: {
|
||||
click: async (context) => {
|
||||
click: async context => {
|
||||
settingStore.checkPlus();
|
||||
const { ui } = useUi();
|
||||
// @ts-ignore
|
||||
let row = context[ui.tableColumn.row];
|
||||
row = _.cloneDeep(row);
|
||||
row = cloneDeep(row);
|
||||
row.title = row.title + "_copy";
|
||||
await crudExpose.openCopy({
|
||||
row: row,
|
||||
index: context.index
|
||||
index: context.index,
|
||||
});
|
||||
},
|
||||
class: "need-plus"
|
||||
class: "need-plus",
|
||||
},
|
||||
config: {
|
||||
order: 1,
|
||||
@@ -346,13 +237,13 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
icon: "ant-design:edit-outlined",
|
||||
click({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "true" } });
|
||||
}
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
order: 2,
|
||||
title: "修改配置/分组",
|
||||
icon: "ant-design:setting-outlined",
|
||||
dropdown: true
|
||||
dropdown: true,
|
||||
},
|
||||
viewCert: {
|
||||
order: 3,
|
||||
@@ -361,7 +252,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
icon: "ph:certificate",
|
||||
async click({ row }) {
|
||||
await viewCert(row);
|
||||
}
|
||||
},
|
||||
},
|
||||
download: {
|
||||
order: 4,
|
||||
@@ -370,13 +261,13 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
icon: "ant-design:download-outlined",
|
||||
async click({ row }) {
|
||||
await downloadCert(row);
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
order: 5,
|
||||
dropdown: true
|
||||
}
|
||||
}
|
||||
dropdown: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
@@ -384,14 +275,14 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 100
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
userId: {
|
||||
title: "用户Id",
|
||||
@@ -399,17 +290,17 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
search: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
})
|
||||
}),
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
}),
|
||||
width: 100
|
||||
}
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
title: "流水线名称",
|
||||
@@ -418,11 +309,11 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
show: true,
|
||||
title: "关键字",
|
||||
component: {
|
||||
name: "a-input"
|
||||
}
|
||||
name: "a-input",
|
||||
},
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
},
|
||||
column: {
|
||||
width: 350,
|
||||
@@ -432,16 +323,16 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
// 注意:必须要on前缀
|
||||
onClick({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
content: {
|
||||
title: "流水线内容",
|
||||
form: { show: false },
|
||||
column: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
valueBuilder({ row }) {
|
||||
if (row.content) {
|
||||
@@ -463,18 +354,18 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
if (row.content) {
|
||||
row.content = JSON.stringify(row.content);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
_triggerCount: {
|
||||
title: "定时任务数",
|
||||
type: "number",
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
_stepCount: {
|
||||
title: "部署任务数",
|
||||
@@ -482,14 +373,14 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
form: { show: false },
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100
|
||||
}
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
lastVars: {
|
||||
title: "到期剩余",
|
||||
type: "number",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
cellRender({ row }) {
|
||||
@@ -501,38 +392,53 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress percent={percent} strokeColor={color} format={(percent: number) => `${leftDays} 天`} />;
|
||||
},
|
||||
width: 150
|
||||
}
|
||||
width: 150,
|
||||
},
|
||||
},
|
||||
"lastVars.certExpiresTime": {
|
||||
title: "过期时间",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 150,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: statusUtil.getOptions()
|
||||
data: statusUtil.getOptions(),
|
||||
}),
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 120,
|
||||
align: "center"
|
||||
}
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
lastHistoryTime: {
|
||||
title: "最后运行",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 150,
|
||||
align: "center"
|
||||
}
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: "启用",
|
||||
@@ -540,12 +446,12 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: "启用" },
|
||||
{ value: true, label: "禁用" }
|
||||
]
|
||||
{ value: true, label: "禁用" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
@@ -553,30 +459,58 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
align: "center",
|
||||
component: {
|
||||
name: "fs-dict-switch",
|
||||
vModel: "checked"
|
||||
vModel: "checked",
|
||||
},
|
||||
async valueChange({ row, key, value }) {
|
||||
return await api.UpdateObj({
|
||||
id: row.id,
|
||||
disabled: row[key]
|
||||
disabled: row[key],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: "分组",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true
|
||||
show: true,
|
||||
},
|
||||
dict: groupDictRef,
|
||||
column: {
|
||||
width: 130,
|
||||
align: "center",
|
||||
component: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
color: "auto",
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "cert", label: "证书申请" },
|
||||
{ value: "cert_upload", label: "证书上传" },
|
||||
{ value: "custom", label: "自定义" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
value: "custom",
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 90,
|
||||
align: "center",
|
||||
show: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
order: {
|
||||
title: "排序号",
|
||||
@@ -584,48 +518,50 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
||||
column: {
|
||||
sorter: true,
|
||||
align: "center",
|
||||
width: 80
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
value: 0
|
||||
}
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
keepHistoryCount: {
|
||||
title: "历史记录保持数",
|
||||
type: "number",
|
||||
form: {
|
||||
value: 20,
|
||||
helper: "历史记录保持条数,多余的会被删除"
|
||||
helper: "历史记录保持条数,多余的会被删除",
|
||||
},
|
||||
column: {
|
||||
width: 130,
|
||||
show: false
|
||||
}
|
||||
show: false,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 155,
|
||||
align: "center"
|
||||
}
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 125,
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
show: false,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<a-tooltip :title="title">
|
||||
<div class="task-shortcut" :title="title" @click="openDialog">
|
||||
<fs-icon :icon="icon" v-bind="attrs"></fs-icon>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { doRequest } from "/@/components/plugins/lib";
|
||||
import { ref, useAttrs, inject } from "vue";
|
||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { mergeWith, isArray } from "lodash-es";
|
||||
defineOptions({
|
||||
name: "TaskShortcut",
|
||||
});
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
const props = defineProps<{
|
||||
icon: string;
|
||||
title: string;
|
||||
action: string;
|
||||
form: any;
|
||||
input: any;
|
||||
pluginName: string;
|
||||
stepId: string;
|
||||
}>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
async function openDialog() {
|
||||
function createCrudOptions() {
|
||||
return {
|
||||
crudOptions: {
|
||||
columns: {
|
||||
...props.form.columns,
|
||||
immediateRun: {
|
||||
title: "立即运行",
|
||||
type: "switch",
|
||||
span: 24,
|
||||
form: {
|
||||
value: true,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
},
|
||||
helper: "保存后是否立即触发运行流水线",
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
wrapper: {
|
||||
title: props.title,
|
||||
saveRemind: false,
|
||||
},
|
||||
afterSubmit() {
|
||||
notification.success({ message: "操作成功" });
|
||||
},
|
||||
async doSubmit({ form }: any) {
|
||||
return await doPluginFormSubmit(form);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const { crudOptions } = createCrudOptions();
|
||||
await openCrudFormDialog({ crudOptions });
|
||||
}
|
||||
|
||||
const getPipelineScope: any = inject("getPipelineScope");
|
||||
const doPluginFormSubmit = async (formData: any) => {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await doRequest({
|
||||
type: "plugin",
|
||||
typeName: props.pluginName,
|
||||
action: props.action,
|
||||
input: props.input,
|
||||
data: formData,
|
||||
});
|
||||
|
||||
if (res.input) {
|
||||
const { save, findStep } = getPipelineScope();
|
||||
const step = findStep(props.stepId);
|
||||
if (step) {
|
||||
// 数组覆盖合并
|
||||
mergeWith(step.input, res.input, (objValue, srcValue) => {
|
||||
if (isArray(objValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
});
|
||||
//保存,但不改变当前编辑状态
|
||||
save(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (formData.immediateRun) {
|
||||
const { run } = getPipelineScope();
|
||||
run();
|
||||
}
|
||||
|
||||
return res;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.task-shortcut {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 0 0 5px 5px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-top: 0;
|
||||
&:hover {
|
||||
background: #fff;
|
||||
border-color: #38a0fb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="task-shortcuts">
|
||||
<TaskShortcut v-for="(item, index) of shortcuts" :key="index" v-bind="item" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import * as pluginApi from "/@/views/certd/pipeline/api.plugin";
|
||||
import TaskShortcut from "./task-shortcut.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "TaskShortcuts",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
task: any;
|
||||
}>();
|
||||
|
||||
watch(
|
||||
() => props.task,
|
||||
value => {
|
||||
init();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const shortcuts = ref([]);
|
||||
async function init() {
|
||||
const steps = props.task?.steps || [];
|
||||
if (steps.length === 0) {
|
||||
return;
|
||||
}
|
||||
const list = [];
|
||||
for (const step of steps) {
|
||||
const stepType = step.type;
|
||||
const pluginDefine = await pluginApi.GetPluginDefine(stepType);
|
||||
if (pluginDefine.shortcut) {
|
||||
for (const key in pluginDefine.shortcut) {
|
||||
const shortcut = pluginDefine.shortcut[key];
|
||||
list.push({
|
||||
...shortcut,
|
||||
pluginName: stepType,
|
||||
input: step.input,
|
||||
stepId: step.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcuts.value = list;
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-drawer v-model:open="stepDrawerVisible" placement="right" :closable="true" width="760px" class="step-form-drawer" :class="{ fullscreen }">
|
||||
<a-drawer v-model:open="stepDrawerVisible" :wrap-style="{ maxWidth: '100vw' }" placement="right" :closable="true" width="760px" class="step-form-drawer" :class="{ fullscreen }">
|
||||
<template #title>
|
||||
<div>
|
||||
编辑步骤
|
||||
@@ -9,7 +9,7 @@
|
||||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<div class="hidden md:block">
|
||||
<fs-icon class="icon-button" :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'" @click="fullscreen = !fullscreen"></fs-icon>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,7 +31,7 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else :gutter="10">
|
||||
<a-col v-for="item of group.plugins" :key="item.key" class="step-plugin">
|
||||
<a-col v-for="item of group.plugins" :key="item.key" class="step-plugin w-full md:w-[50%]">
|
||||
<a-card
|
||||
hoverable
|
||||
:class="{ current: item.name === currentStep.type }"
|
||||
@@ -413,6 +413,10 @@ export default {
|
||||
|
||||
<style lang="less">
|
||||
.step-form-drawer {
|
||||
max-width: 100%;
|
||||
.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
&.fullscreen {
|
||||
.pi-step-form {
|
||||
.body {
|
||||
@@ -437,6 +441,10 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.pi-step-form {
|
||||
.bottom-button {
|
||||
padding: 20px;
|
||||
@@ -456,7 +464,6 @@ export default {
|
||||
padding: 0px;
|
||||
|
||||
.step-plugin {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.ant-tabs-content {
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
|
||||
<div class="layout">
|
||||
<div class="layout-left">
|
||||
<div class="pipeline-container bg-neutral-100 dark:bg-black">
|
||||
<div ref="pipelineContainer" class="pipeline-container bg-neutral-100 dark:bg-black">
|
||||
<div class="pipeline">
|
||||
<v-draggable v-model="pipeline.stages" class="stages" item-key="id" handle=".stage-move-handle" :disabled="!settingStore.isPlus">
|
||||
<template #header>
|
||||
<div class="stage first-stage">
|
||||
<div class="title stage-move-handle">
|
||||
<div class="title">
|
||||
<text-editable model-value="触发源" :disabled="true" />
|
||||
</div>
|
||||
<div class="tasks">
|
||||
@@ -67,19 +67,19 @@
|
||||
|
||||
<template #item="{ element: stage, index }">
|
||||
<div :key="stage.id" class="stage" :class="{ 'last-stage': isLastStage(index), ['stage_' + index]: true }">
|
||||
<div class="title">
|
||||
<div class="title" @mousedown.stop>
|
||||
<text-editable v-model="stage.title" :disabled="!editMode"></text-editable>
|
||||
<div v-plus class="icon-box stage-move-handle">
|
||||
<fs-icon v-if="editMode" title="拖动排序" icon="ion:move-outline"></fs-icon>
|
||||
</div>
|
||||
</div>
|
||||
<v-draggable v-model="stage.tasks" item-key="id" class="tasks" group="task" handle=".task-move-handle" :disabled="!settingStore.isPlus">
|
||||
<v-draggable v-model="stage.tasks" item-key="id" class="tasks" group="task" handle=".task-move-handle" :disabled="!settingStore.isPlus" @mousedown.stop>
|
||||
<template #item="{ element: task, index: taskIndex }">
|
||||
<div
|
||||
class="task-container"
|
||||
:class="{
|
||||
'first-task': taskIndex === 0,
|
||||
'validate-error': hasValidateError(task.id)
|
||||
'validate-error': hasValidateError(task.id),
|
||||
}"
|
||||
>
|
||||
<div class="line line-left">
|
||||
@@ -104,7 +104,7 @@
|
||||
</template>
|
||||
<span class="flex-o w-100">
|
||||
<span class="ellipsis flex-1 task-title" :class="{ 'in-edit': editMode, deleted: task.disabled }">{{ task.title }}</span>
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
<pi-status-show v-if="!editMode" :status="task.status?.result"></pi-status-show>
|
||||
</span>
|
||||
</a-popover>
|
||||
</a-button>
|
||||
@@ -114,6 +114,9 @@
|
||||
<div v-plus class="icon-box task-move-handle action drag">
|
||||
<fs-icon v-if="editMode" title="拖动排序" icon="ion:move-outline"></fs-icon>
|
||||
</div>
|
||||
<div class="shortcut">
|
||||
<TaskShortcuts :task="task" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -269,29 +272,31 @@ import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/hi
|
||||
import { FsIcon } from "@fast-crud/fast-crud";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
|
||||
import { eachSteps, findStep } from "../utils";
|
||||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm, VDraggable },
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm, VDraggable, TaskShortcuts },
|
||||
props: {
|
||||
pipelineId: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
historyId: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object as PropType<PipelineOptions>,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "update:editMode"],
|
||||
setup(props, ctx) {
|
||||
@@ -341,8 +346,9 @@ export default defineComponent({
|
||||
histories.value = historyList;
|
||||
|
||||
if (historyList.length > 0) {
|
||||
//@ts-ignore
|
||||
if (props.historyId > 0) {
|
||||
const found = historyList.find((item) => {
|
||||
const found = historyList.find(item => {
|
||||
//字符串==int
|
||||
return item.id == props.historyId;
|
||||
});
|
||||
@@ -383,7 +389,7 @@ export default defineComponent({
|
||||
() => {
|
||||
return props.editMode;
|
||||
},
|
||||
(editMode) => {
|
||||
editMode => {
|
||||
if (editMode) {
|
||||
changeCurrentHistory();
|
||||
} else if (histories.value.length > 0) {
|
||||
@@ -407,7 +413,7 @@ export default defineComponent({
|
||||
await loadHistoryList(true);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -438,7 +444,7 @@ export default defineComponent({
|
||||
};
|
||||
return {
|
||||
taskViewOpen,
|
||||
taskViewRef
|
||||
taskViewRef,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -503,7 +509,7 @@ export default defineComponent({
|
||||
id: nanoid(),
|
||||
title: "新阶段",
|
||||
tasks: [],
|
||||
status: null
|
||||
status: null,
|
||||
};
|
||||
//stage: any, stageIndex: number, onSuccess
|
||||
useTaskRet.taskAdd(stage, stageIndex, () => {
|
||||
@@ -519,7 +525,7 @@ export default defineComponent({
|
||||
}
|
||||
return {
|
||||
stageAdd,
|
||||
isLastStage
|
||||
isLastStage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -551,7 +557,7 @@ export default defineComponent({
|
||||
return {
|
||||
triggerAdd,
|
||||
triggerEdit,
|
||||
triggerFormRef
|
||||
triggerFormRef,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -586,7 +592,7 @@ export default defineComponent({
|
||||
return {
|
||||
notificationAdd,
|
||||
notificationEdit,
|
||||
notificationFormRef
|
||||
notificationFormRef,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -604,7 +610,7 @@ export default defineComponent({
|
||||
},
|
||||
onCancel() {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
if (!res) {
|
||||
@@ -628,7 +634,7 @@ export default defineComponent({
|
||||
watchNewHistoryList();
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id, stepId: stepId });
|
||||
notification.success({ message: "管道已经开始运行" });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -642,31 +648,18 @@ export default defineComponent({
|
||||
validateErrors.value[taskId] = errors;
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
function doValidate() {
|
||||
validateErrors.value = {};
|
||||
|
||||
const stepIds: string[] = [];
|
||||
//校验output id是否正确
|
||||
const pp = pipeline.value;
|
||||
function eachSteps(callback: any) {
|
||||
if (pp.stages) {
|
||||
for (const stage of pp.stages) {
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
callback(step, task, stage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//检查输出的stepid是否存在
|
||||
let hasError = false;
|
||||
let errorMessage = "";
|
||||
eachSteps((step: any, task: any, stage: any) => {
|
||||
eachSteps(pp, (step: any, task: any, stage: any) => {
|
||||
stepIds.push(step.id);
|
||||
if (step.input) {
|
||||
for (const key in step.input) {
|
||||
@@ -684,10 +677,10 @@ export default defineComponent({
|
||||
hasError = true;
|
||||
const message = `任务${step.title}的前置输出步骤${paramName}不存在,请重新修改此任务`;
|
||||
addValidateError(task.id, {
|
||||
message
|
||||
message,
|
||||
});
|
||||
addValidateError(step.id, {
|
||||
message
|
||||
message,
|
||||
});
|
||||
errorMessage += message + ";";
|
||||
}
|
||||
@@ -704,7 +697,7 @@ export default defineComponent({
|
||||
function hasValidateError(taskId: string) {
|
||||
return validateErrors.value[taskId] != null;
|
||||
}
|
||||
const save = async () => {
|
||||
const save = async (offEdit = true) => {
|
||||
doValidate();
|
||||
|
||||
saveLoading.value = true;
|
||||
@@ -723,7 +716,9 @@ export default defineComponent({
|
||||
|
||||
await props.options.doSave(pipeline.value);
|
||||
}
|
||||
toggleEditMode(false);
|
||||
if (offEdit) {
|
||||
toggleEditMode(false);
|
||||
}
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
@@ -738,13 +733,18 @@ export default defineComponent({
|
||||
toggleEditMode(false);
|
||||
};
|
||||
|
||||
function fundStepFromPipeline(id: string) {
|
||||
return findStep(pipeline.value, id);
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
save,
|
||||
edit,
|
||||
cancel,
|
||||
saveLoading,
|
||||
hasValidateError
|
||||
hasValidateError,
|
||||
findStep: fundStepFromPipeline,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -767,14 +767,66 @@ export default defineComponent({
|
||||
historyView,
|
||||
historyCancel,
|
||||
logsCollapse,
|
||||
toggleLogsCollapse
|
||||
toggleLogsCollapse,
|
||||
};
|
||||
}
|
||||
|
||||
function useScroll() {
|
||||
const pipelineContainer = ref();
|
||||
onMounted(() => {
|
||||
if (pipelineContainer.value) {
|
||||
const scrollableDiv = pipelineContainer.value;
|
||||
let isDragging = false;
|
||||
let startX: any = null;
|
||||
let scrollLeft: any = null;
|
||||
|
||||
scrollableDiv.addEventListener("mousedown", (e: any) => {
|
||||
isDragging = true;
|
||||
startX = e.pageX - scrollableDiv.offsetLeft;
|
||||
scrollLeft = scrollableDiv.scrollLeft;
|
||||
scrollableDiv.style.cursor = "grabbing"; // 按住时变成抓手
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
scrollableDiv.addEventListener("mouseleave", () => {
|
||||
isDragging = false;
|
||||
scrollableDiv.style.cursor = "grab"; // 离开时恢复光标
|
||||
});
|
||||
|
||||
scrollableDiv.addEventListener("mouseup", () => {
|
||||
isDragging = false;
|
||||
scrollableDiv.style.cursor = "grab"; // 松开时恢复光标
|
||||
});
|
||||
|
||||
scrollableDiv.addEventListener("mousemove", (e: any) => {
|
||||
if (!isDragging) return; // 如果没有按住鼠标,退出
|
||||
e.preventDefault();
|
||||
const x = e.pageX - scrollableDiv.offsetLeft;
|
||||
const walk = (x - startX) * 2; // 移动的速度
|
||||
scrollableDiv.scrollLeft = scrollLeft - walk; // 更新滚动位置
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { pipelineContainer };
|
||||
}
|
||||
|
||||
const useTaskRet = useTask();
|
||||
const useStageRet = useStage(useTaskRet);
|
||||
const settingStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const actions = useActions();
|
||||
const trigger = useTrigger();
|
||||
provide("getPipelineScope", () => {
|
||||
return {
|
||||
run: actions.run,
|
||||
pipeline: pipeline,
|
||||
save: actions.save,
|
||||
findStep: actions.findStep,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
pipeline,
|
||||
currentHistory,
|
||||
@@ -784,12 +836,13 @@ export default defineComponent({
|
||||
settingStore,
|
||||
...useTaskRet,
|
||||
...useStageRet,
|
||||
...useTrigger(),
|
||||
...useActions(),
|
||||
...trigger,
|
||||
...actions,
|
||||
...useHistory(),
|
||||
...useNotification()
|
||||
...useNotification(),
|
||||
...useScroll(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@@ -992,8 +1045,8 @@ export default defineComponent({
|
||||
|
||||
.action {
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
top: 18px;
|
||||
right: 10px;
|
||||
top: 17px;
|
||||
//font-size: 18px;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
@@ -1001,10 +1054,10 @@ export default defineComponent({
|
||||
color: #1890ff;
|
||||
}
|
||||
&.copy {
|
||||
right: 80px;
|
||||
right: 30px;
|
||||
}
|
||||
&.drag {
|
||||
right: 60px;
|
||||
right: 10px;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
@@ -1012,6 +1065,13 @@ export default defineComponent({
|
||||
.ant-btn {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
.shortcut {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { forEach } from "lodash-es";
|
||||
|
||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
//@ts-ignore
|
||||
import forge from "node-forge";
|
||||
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
|
||||
if (!list || list.length <= 0) {
|
||||
return;
|
||||
}
|
||||
forEach(list, (item) => {
|
||||
forEach(list, item => {
|
||||
exec(item, runnableType);
|
||||
if (runnableType === "stage") {
|
||||
eachStages(item.tasks, exec, "task");
|
||||
@@ -13,3 +17,86 @@ export function eachStages(list: any[], exec: (item: any, runnableType: string)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function eachSteps(pipeline: any, callback: any) {
|
||||
const pp = pipeline;
|
||||
if (pp.stages) {
|
||||
for (const stage of pp.stages) {
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
callback(step, task, stage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findStep(pipeline: any, id: string) {
|
||||
const pp = pipeline;
|
||||
if (pp.stages) {
|
||||
for (const stage of pp.stages) {
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
if (step.id === id) {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkPipelineLimit() {
|
||||
const settingsStore = useSettingStore();
|
||||
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
|
||||
//检查数量是否超限
|
||||
|
||||
const suiteDetail = await mySuiteApi.SuiteDetailGet();
|
||||
const max = suiteDetail.pipelineCount.max;
|
||||
if (max != -1 && max <= suiteDetail.pipelineCount.used) {
|
||||
notification.error({
|
||||
message: `对不起,您最多只能创建${max}条流水线,请购买或升级套餐`,
|
||||
});
|
||||
throw new Error("流水线数量超限");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function readCertDetail(crt: string) {
|
||||
const detail = forge.pki.certificateFromPem(crt);
|
||||
const expires = detail.notAfter;
|
||||
return { detail, expires };
|
||||
}
|
||||
|
||||
export function getAllDomainsFromCrt(crt: string) {
|
||||
const { detail } = readCertDetail(crt);
|
||||
const domains = [];
|
||||
|
||||
// 1. 提取SAN中的DNS名称
|
||||
const sanExtension = detail.extensions.find((ext: any) => ext.name === "subjectAltName");
|
||||
if (sanExtension) {
|
||||
sanExtension.altNames.forEach((altName: any) => {
|
||||
if (altName.type === 2) {
|
||||
// type=2 表示DNS名称
|
||||
domains.push(altName.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 如果没有SAN,回退到CN(通用名称)
|
||||
if (domains.length === 0) {
|
||||
const cnAttr = detail.subject.attributes.find((attr: any) => attr.name === "commonName");
|
||||
if (cnAttr) {
|
||||
domains.push(cnAttr.value);
|
||||
}
|
||||
}
|
||||
return domains;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/ui/certd-server/.prettierrc
Normal file
7
packages/ui/certd-server/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 220,
|
||||
"bracketSpacing": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
|
||||
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
|
||||
* 站点监控,手动测试也发通知 ([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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.31.3",
|
||||
"version": "1.31.4",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -38,18 +38,18 @@
|
||||
"@aws-sdk/client-acm": "^3.699.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||
"@aws-sdk/client-s3": "^3.705.0",
|
||||
"@certd/acme-client": "^1.31.3",
|
||||
"@certd/basic": "^1.31.3",
|
||||
"@certd/commercial-core": "^1.31.3",
|
||||
"@certd/lib-huawei": "^1.31.3",
|
||||
"@certd/lib-k8s": "^1.31.3",
|
||||
"@certd/lib-server": "^1.31.3",
|
||||
"@certd/midway-flyway-js": "^1.31.3",
|
||||
"@certd/pipeline": "^1.31.3",
|
||||
"@certd/plugin-cert": "^1.31.3",
|
||||
"@certd/plugin-lib": "^1.31.3",
|
||||
"@certd/plugin-plus": "^1.31.3",
|
||||
"@certd/plus-core": "^1.31.3",
|
||||
"@certd/acme-client": "^1.31.4",
|
||||
"@certd/basic": "^1.31.4",
|
||||
"@certd/commercial-core": "^1.31.4",
|
||||
"@certd/lib-huawei": "^1.31.4",
|
||||
"@certd/lib-k8s": "^1.31.4",
|
||||
"@certd/lib-server": "^1.31.4",
|
||||
"@certd/midway-flyway-js": "^1.31.4",
|
||||
"@certd/pipeline": "^1.31.4",
|
||||
"@certd/plugin-cert": "^1.31.4",
|
||||
"@certd/plugin-lib": "^1.31.4",
|
||||
"@certd/plugin-plus": "^1.31.4",
|
||||
"@certd/plus-core": "^1.31.4",
|
||||
"@corsinvest/cv4pve-api-javascript": "^8.3.0",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user