From de40be430ba4d5ff7ec8c60571260b2603e974ca Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 18 Mar 2025 00:52:50 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=94=AF=E6=8C=81=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AF=81=E4=B9=A6=E5=B9=B6=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/acme-client/.prettierrc | 7 + packages/core/basic/.eslintrc | 4 +- packages/core/basic/.prettierrc | 7 + packages/core/basic/src/utils/util.hash.ts | 6 +- packages/core/pipeline/.eslintrc | 4 +- packages/core/pipeline/.prettierrc | 6 +- packages/core/pipeline/src/core/executor.ts | 5 +- packages/core/pipeline/src/plugin/api.ts | 6 +- packages/core/pipeline/src/service/index.ts | 3 + packages/libs/lib-huawei/.eslintrc | 3 +- packages/libs/lib-huawei/.prettierrc | 6 +- packages/libs/lib-iframe/.eslintrc | 3 +- packages/libs/lib-iframe/.prettierrc | 4 +- packages/libs/lib-k8s/.prettierrc | 4 +- packages/libs/lib-server/.prettierrc | 4 +- packages/libs/midway-flyway-js/.prettierrc | 4 +- packages/plugins/plugin-cert/.eslintrc | 3 +- packages/plugins/plugin-cert/.prettierrc | 6 +- .../src/plugin/cert-plugin/base-convert.ts | 177 +++++++++++++++ .../src/plugin/cert-plugin/base.ts | 168 +------------- .../src/plugin/cert-plugin/custom/index.ts | 69 ++++-- .../plugins/plugin-cert/src/plugin/index.ts | 2 + packages/plugins/plugin-lib/.eslintrc | 3 +- packages/plugins/plugin-lib/.prettierrc | 6 +- packages/ui/certd-client/.eslintrc.js | 42 ++-- packages/ui/certd-client/.prettierrc | 8 +- .../src/components/plugins/index.ts | 3 + .../src/views/certd/monitor/cert/api.ts | 26 ++- .../src/views/certd/monitor/cert/crud.tsx | 160 +++++++------- .../certd/monitor/cert/selector/index.vue | 123 +++++++++++ .../notification-selector/index.vue | 33 +-- .../views/certd/pipeline/certd-form/crud.tsx | 50 ++--- .../src/views/certd/pipeline/crud.tsx | 208 +++++++++--------- packages/ui/certd-server/.eslintrc | 4 +- packages/ui/certd-server/.prettierrc | 7 + .../user/monitor/cert-info-controller.ts | 47 +++- .../monitor/service/cert-info-service.ts | 28 +-- .../monitor/service/cert-upload-service.ts | 178 +++++++++++++++ .../pipeline/service/pipeline-service.ts | 74 ++++--- .../plugin/deploy-to-alb/index.ts | 4 +- .../plugin/deploy-to-cdn/index.ts | 4 +- .../plugin/deploy-to-dcdn/index.ts | 4 +- .../plugin/deploy-to-fc/index.ts | 4 +- .../plugin/deploy-to-nlb/index.ts | 4 +- .../plugin/deploy-to-oss/index.ts | 3 +- .../plugin/deploy-to-slb/index.ts | 4 +- .../plugin/deploy-to-waf/index.ts | 4 +- .../plugin/upload-to-aliyun/index.ts | 4 +- .../plugins/plugin-deploy-to-cloudfront.ts | 4 +- .../plugins/plugin-upload-to-acm.ts | 4 +- .../plugins/plugin-deploy-to-cdn.ts | 4 +- .../plugin-demo/plugins/plugin-test.ts | 4 +- .../plugins/deploy-to-cdn/index.ts | 4 +- .../plugin-gcore/plugins/plugin-flush.ts | 4 +- .../plugin-gcore/plugins/plugin-upload.ts | 4 +- .../plugin-host/plugin/copy-to-local/index.ts | 4 +- .../plugin/host-shell-execute/index.ts | 1 - .../plugin/upload-to-host/index.ts | 4 +- .../plugins/deploy-to-cdn/index.ts | 4 +- .../plugin-other/plugins/plugin-script.ts | 4 +- .../plugin-proxmox/plugins/plugin-upload.ts | 4 +- .../plugin/deploy-to-cdn/index.ts | 4 +- .../plugin-qiniu/plugin/upload-cert/index.ts | 4 +- .../plugin-qnap/plugins/plugin-qnap.ts | 4 +- .../plugin/deploy-to-cdn-v2/index.ts | 3 +- .../plugin/deploy-to-cdn/index.ts | 3 +- .../plugin/deploy-to-clb/index.ts | 3 +- .../plugin/deploy-to-cos/index.ts | 4 +- .../plugin/deploy-to-eo/index.ts | 5 +- .../plugin/deploy-to-live/index.ts | 4 +- .../plugin/deploy-to-tke-ingress/index.ts | 4 +- .../plugin/start-instances/index.ts | 6 +- .../plugin/upload-to-tencent/index.ts | 4 +- .../plugins/plugin-deploy-to-cdn.ts | 4 +- 74 files changed, 1040 insertions(+), 597 deletions(-) create mode 100644 packages/core/acme-client/.prettierrc create mode 100644 packages/core/basic/.prettierrc create mode 100644 packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts create mode 100644 packages/ui/certd-client/src/views/certd/monitor/cert/selector/index.vue create mode 100644 packages/ui/certd-server/.prettierrc create mode 100644 packages/ui/certd-server/src/modules/monitor/service/cert-upload-service.ts diff --git a/packages/core/acme-client/.prettierrc b/packages/core/acme-client/.prettierrc new file mode 100644 index 00000000..3b1e8719 --- /dev/null +++ b/packages/core/acme-client/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/packages/core/basic/.eslintrc b/packages/core/basic/.eslintrc index 6d1374ff..eeb127a3 100644 --- a/packages/core/basic/.eslintrc +++ b/packages/core/basic/.eslintrc @@ -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" } } diff --git a/packages/core/basic/.prettierrc b/packages/core/basic/.prettierrc new file mode 100644 index 00000000..3b1e8719 --- /dev/null +++ b/packages/core/basic/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/packages/core/basic/src/utils/util.hash.ts b/packages/core/basic/src/utils/util.hash.ts index 8ba3e80a..0557c324 100644 --- a/packages/core/basic/src/utils/util.hash.ts +++ b/packages/core/basic/src/utils/util.hash.ts @@ -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, }; diff --git a/packages/core/pipeline/.eslintrc b/packages/core/pipeline/.eslintrc index 6d1374ff..eeb127a3 100644 --- a/packages/core/pipeline/.eslintrc +++ b/packages/core/pipeline/.eslintrc @@ -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" } } diff --git a/packages/core/pipeline/.prettierrc b/packages/core/pipeline/.prettierrc index 1d96ee86..3b1e8719 100644 --- a/packages/core/pipeline/.prettierrc +++ b/packages/core/pipeline/.prettierrc @@ -1,3 +1,7 @@ { - "printWidth": 160 + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" } \ No newline at end of file diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 79193527..8f4f9864 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -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,7 +33,7 @@ export type ExecutorOptions = { user: UserInfo; baseURL?: string; sysInfo?: SysInfo; - serviceGetter: (name: string) => any; + serviceGetter: IServiceGetter; }; export class Executor { @@ -366,6 +366,7 @@ export class Executor { step, pipeline: this.pipeline, }), + serviceGetter: this.options.serviceGetter, }; instance.setCtx(taskCtx); diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index 4af68b6d..66e95174 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -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"; @@ -116,7 +116,7 @@ export type TaskInstanceContext = { emitter: TaskEmitter; //service 容器 - serviceContainer?: Record; + serviceGetter?: IServiceGetter; }; export abstract class AbstractTaskPlugin implements ITaskPlugin { @@ -224,7 +224,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; diff --git a/packages/core/pipeline/src/service/index.ts b/packages/core/pipeline/src/service/index.ts index 71be9752..2cf4395e 100644 --- a/packages/core/pipeline/src/service/index.ts +++ b/packages/core/pipeline/src/service/index.ts @@ -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; +}; diff --git a/packages/libs/lib-huawei/.eslintrc b/packages/libs/lib-huawei/.eslintrc index a591578c..eeb127a3 100644 --- a/packages/libs/lib-huawei/.eslintrc +++ b/packages/libs/lib-huawei/.eslintrc @@ -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" } } diff --git a/packages/libs/lib-huawei/.prettierrc b/packages/libs/lib-huawei/.prettierrc index 1d96ee86..3b1e8719 100644 --- a/packages/libs/lib-huawei/.prettierrc +++ b/packages/libs/lib-huawei/.prettierrc @@ -1,3 +1,7 @@ { - "printWidth": 160 + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" } \ No newline at end of file diff --git a/packages/libs/lib-iframe/.eslintrc b/packages/libs/lib-iframe/.eslintrc index a591578c..eeb127a3 100644 --- a/packages/libs/lib-iframe/.eslintrc +++ b/packages/libs/lib-iframe/.eslintrc @@ -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" } } diff --git a/packages/libs/lib-iframe/.prettierrc b/packages/libs/lib-iframe/.prettierrc index 903b313d..69b7d9c3 100644 --- a/packages/libs/lib-iframe/.prettierrc +++ b/packages/libs/lib-iframe/.prettierrc @@ -1,7 +1,7 @@ { - "printWidth": 160, + "printWidth": 220, "bracketSpacing": true, - "singleQuote": true, + "singleQuote": false, "trailingComma": "es5", "arrowParens": "avoid" } diff --git a/packages/libs/lib-k8s/.prettierrc b/packages/libs/lib-k8s/.prettierrc index 903b313d..69b7d9c3 100644 --- a/packages/libs/lib-k8s/.prettierrc +++ b/packages/libs/lib-k8s/.prettierrc @@ -1,7 +1,7 @@ { - "printWidth": 160, + "printWidth": 220, "bracketSpacing": true, - "singleQuote": true, + "singleQuote": false, "trailingComma": "es5", "arrowParens": "avoid" } diff --git a/packages/libs/lib-server/.prettierrc b/packages/libs/lib-server/.prettierrc index 903b313d..69b7d9c3 100644 --- a/packages/libs/lib-server/.prettierrc +++ b/packages/libs/lib-server/.prettierrc @@ -1,7 +1,7 @@ { - "printWidth": 160, + "printWidth": 220, "bracketSpacing": true, - "singleQuote": true, + "singleQuote": false, "trailingComma": "es5", "arrowParens": "avoid" } diff --git a/packages/libs/midway-flyway-js/.prettierrc b/packages/libs/midway-flyway-js/.prettierrc index 903b313d..69b7d9c3 100644 --- a/packages/libs/midway-flyway-js/.prettierrc +++ b/packages/libs/midway-flyway-js/.prettierrc @@ -1,7 +1,7 @@ { - "printWidth": 160, + "printWidth": 220, "bracketSpacing": true, - "singleQuote": true, + "singleQuote": false, "trailingComma": "es5", "arrowParens": "avoid" } diff --git a/packages/plugins/plugin-cert/.eslintrc b/packages/plugins/plugin-cert/.eslintrc index a591578c..eeb127a3 100644 --- a/packages/plugins/plugin-cert/.eslintrc +++ b/packages/plugins/plugin-cert/.eslintrc @@ -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" } } diff --git a/packages/plugins/plugin-cert/.prettierrc b/packages/plugins/plugin-cert/.prettierrc index 1d96ee86..3b1e8719 100644 --- a/packages/plugins/plugin-cert/.prettierrc +++ b/packages/plugins/plugin-cert/.prettierrc @@ -1,3 +1,7 @@ { - "printWidth": 160 + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" } \ No newline at end of file diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts new file mode 100644 index 00000000..04a21f00 --- /dev/null +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts @@ -0,0 +1,177 @@ +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 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; + + 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; + } +} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts index fb2ae13e..62b3c546 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -1,10 +1,8 @@ -import { AbstractTaskPlugin, IContext, NotificationBody, Step, TaskEmitter, TaskInput, TaskOutput } from "@certd/pipeline"; +import { NotificationBody, Step, TaskEmitter, 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"; @@ -12,30 +10,7 @@ export async function emitCertApplySuccess(emitter: TaskEmitter, cert: CertReade 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: "邮箱", @@ -50,36 +25,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 +56,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; @@ -151,89 +88,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 +133,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 { const cert = this.lastStatus?.status?.output?.cert; if (cert == null) { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts index e8c34094..c2105817 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts @@ -1,38 +1,53 @@ -import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput } from "@certd/pipeline"; +import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput, TaskOutput } from "@certd/pipeline"; import type { CertInfo } from "../acme.js"; import { CertReader } from "../cert-reader.js"; -import { CertApplyBasePlugin } from "../base.js"; +import { CertApplyBaseConvertPlugin } from "../base-convert.js"; +import dayjs from "dayjs"; export { CertReader }; export type { CertInfo }; @IsTaskPlugin({ - name: "CertUpload", + name: "CertApplyUpload", icon: "ph:certificate", title: "证书手动上传", group: pluginGroups.cert.key, desc: "在证书仓库手动上传后触发部署证书", default: { - input: { - renewDays: 35, - forceUpdate: false, - }, strategy: { runStrategy: RunStrategy.AlwaysRun, }, }, }) -export class CertUploadPlugin extends CertApplyBasePlugin { +export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin { @TaskInput({ title: "证书仓库ID", component: { - name: "a-cert-select", - vModel: "value", + name: "cert-info-selector", + vModel: "modelValue", }, + order: -9999, required: true, + mergeScript: ` + return { + component:{ + on:{ + selectedChange(scope){ + console.log(scope) + scope.form.input.domains = scope.$event?.domains + } + } + } + } + `, }) certInfoId!: string; + @TaskOutput({ + title: "证书MD5", + }) + certMd5?: string; + async onInstance() { this.accessService = this.ctx.accessService; this.logger = this.ctx.logger; @@ -41,21 +56,45 @@ export class CertUploadPlugin extends CertApplyBasePlugin { } async onInit(): Promise {} - async doCertApply() { - const siteInfoService = this.ctx.serviceContainer["CertInfoService"]; + async getCertFromStore() { + const siteInfoService = await this.ctx.serviceGetter.get("CertInfoService"); const certInfo = await siteInfoService.getCertInfo({ certId: this.certInfoId, - userid: this.pipeline.userId, + userId: this.pipeline.userId, }); const certReader = new CertReader(certInfo); if (!certReader.expires && certReader.expires < new Date().getTime()) { - throw new Error("证书已过期,停止部署"); + throw new Error("证书已过期,停止部署,请重新上传证书"); } return certReader; } + + async execute(): Promise { + 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}天`); + 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("证书有变化,重新部署"); + this.clearLastStatus(); + //输出证书MD5 + this.certMd5 = crtMd5; + await this.output(certReader, true); + return; + } } -new CertUploadPlugin(); +new CertApplyUploadPlugin(); diff --git a/packages/plugins/plugin-cert/src/plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/index.ts index aaafb350..4cf3f4aa 100644 --- a/packages/plugins/plugin-cert/src/plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/index.ts @@ -1,2 +1,4 @@ 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"]; diff --git a/packages/plugins/plugin-lib/.eslintrc b/packages/plugins/plugin-lib/.eslintrc index a591578c..eeb127a3 100644 --- a/packages/plugins/plugin-lib/.eslintrc +++ b/packages/plugins/plugin-lib/.eslintrc @@ -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" } } diff --git a/packages/plugins/plugin-lib/.prettierrc b/packages/plugins/plugin-lib/.prettierrc index 1d96ee86..3b1e8719 100644 --- a/packages/plugins/plugin-lib/.prettierrc +++ b/packages/plugins/plugin-lib/.prettierrc @@ -1,3 +1,7 @@ { - "printWidth": 160 + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" } \ No newline at end of file diff --git a/packages/ui/certd-client/.eslintrc.js b/packages/ui/certd-client/.eslintrc.js index 93191922..c5e62e7b 100644 --- a/packages/ui/certd-client/.eslintrc.js +++ b/packages/ui/certd-client/.eslintrc.js @@ -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", // }, // ], - } + }, }; diff --git a/packages/ui/certd-client/.prettierrc b/packages/ui/certd-client/.prettierrc index 61e99f7c..69b7d9c3 100644 --- a/packages/ui/certd-client/.prettierrc +++ b/packages/ui/certd-client/.prettierrc @@ -1,5 +1,7 @@ { - - "trailingComma": "none", - "printWidth": 220 + "printWidth": 220, + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "es5", + "arrowParens": "avoid" } diff --git a/packages/ui/certd-client/src/components/plugins/index.ts b/packages/ui/certd-client/src/components/plugins/index.ts index 4c8bc027..7e9af4d8 100644 --- a/packages/ui/certd-client/src/components/plugins/index.ts +++ b/packages/ui/certd-client/src/components/plugins/index.ts @@ -5,6 +5,7 @@ import OutputSelector from "/@/components/plugins/common/output-selector/index.v import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue"; import DomainsVerifyPlanEditor from "/@/components/plugins/cert/domains-verify-plan-editor/index.vue"; import AccessSelector from "/@/views/certd/access/access-selector/index.vue"; +import CertInfoSelector from "/@/views/certd/monitor/cert/selector/index.vue"; import InputPassword from "./common/input-password.vue"; import ApiTest from "./common/api-test.vue"; export * from "./cert/index.js"; @@ -14,6 +15,8 @@ export default { app.component("DnsProviderSelector", DnsProviderSelector); app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor); app.component("AccessSelector", AccessSelector); + app.component("CertInfoSelector", CertInfoSelector); + app.component("ApiTest", ApiTest); app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter); diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/api.ts b/packages/ui/certd-client/src/views/certd/monitor/cert/api.ts index 70b02370..e4df5132 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/cert/api.ts +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/api.ts @@ -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 { return await request({ url: apiPrefix + "/getCert", method: "post", - params: { id: id } + params: { id: id }, }); - } + }, + + async GetOptionsByIds(ids: number[]): Promise { + return await request({ + url: apiPrefix + "/getOptionsByIds", + method: "post", + data: { ids }, + }); + }, }; diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx index bc9ef231..e6654811 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx @@ -1,6 +1,17 @@ // @ts-ignore import { useI18n } from "vue-i18n"; -import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, 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 { useRouter } from "vue-router"; @@ -9,7 +20,6 @@ import { notification } from "ant-design-vue"; import CertView from "/@/views/certd/pipeline/cert-view.vue"; export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { - const { t } = useI18n(); const api = certInfoApi; const pageRequest = async (query: UserPageQuery): Promise => { return await api.GetList(query); @@ -48,7 +58,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat width: 800, content: () => { return ; - } + }, }); }; @@ -69,35 +79,35 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat title: "ID", type: "number", form: { - show: false - } + show: false, + }, }, "cert.crt": { title: "证书", type: "textarea", form: { component: { - rows: 4 + rows: 4, }, rules: [{ required: true, message: "此项必填" }], - col: { span: 24 } - } + col: { span: 24 }, + }, }, "cert.key": { title: "私钥", type: "textarea", form: { component: { - rows: 4 + rows: 4, }, rules: [{ required: true, message: "此项必填" }], - col: { span: 24 } - } - } + col: { span: 24 }, + }, + }, }, form: { wrapper: { - title: "上传自定义证书" + title: "上传自定义证书", }, async doSubmit({ form }: any) { if (!id) { @@ -106,9 +116,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat form.id = id; } return await api.Upload(form); - } - } - } + }, + }, + }, }; } const { crudOptions } = createCrudOptions(); @@ -121,22 +131,22 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat 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, @@ -147,13 +157,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat show: true, async click() { await openUpload(); - } - } - } + }, + }, + }, }, tabs: { name: "fromType", - show: true + show: true, }, rowHandle: { width: 140, @@ -167,7 +177,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat icon: "ph:certificate", async click({ row }) { await viewCert(row); - } + }, }, copy: { show: false }, edit: { show: false }, @@ -181,15 +191,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat icon: "ph:upload", async click({ row }) { await openUpload(row.id); - } + }, }, remove: { order: 10, show: compute(({ row }) => { return row.fromType === "upload"; - }) - } - } + }), + }, + }, }, columns: { id: { @@ -197,85 +207,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, + }, }, fromType: { title: "来源", search: { - show: true + show: true, }, type: "dict-select", dict: dict({ data: [ { label: "流水线", value: "pipeline" }, - { label: "手动上传", value: "upload" } - ] + { label: "手动上传", value: "upload" }, + ], }), form: { - show: false + show: false, }, column: { width: 100, sorter: true, component: { - color: "auto" + color: "auto", }, - conditionalRender: false + conditionalRender: false, }, valueBuilder({ value, row, key }) { if (!value) { row[key] = "pipeline"; } - } + }, }, domains: { title: "域名", search: { - show: true + 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, @@ -290,54 +300,54 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const color = leftDays < 20 ? "red" : "#389e0d"; const percent = (leftDays / 90) * 100; return `${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, @@ -346,12 +356,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat on: { onClick({ row }) { router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } }); - } - } - } - } - } - } - } + }, + }, + }, + }, + }, + }, + }, }; } diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/selector/index.vue b/packages/ui/certd-client/src/views/certd/monitor/cert/selector/index.vue new file mode 100644 index 00000000..df791d50 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/selector/index.vue @@ -0,0 +1,123 @@ + + + + diff --git a/packages/ui/certd-client/src/views/certd/notification/notification-selector/index.vue b/packages/ui/certd-client/src/views/certd/notification/notification-selector/index.vue index c9b12ef6..faddb7f2 100644 --- a/packages/ui/certd-client/src/views/certd/notification/notification-selector/index.vue +++ b/packages/ui/certd-client/src/views/certd/notification/notification-selector/index.vue @@ -1,16 +1,7 @@