From d43bc3e427e4fd8c88c23744fe81d32d3af86b63 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:01:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=81=E4=B9=A6=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8A=E4=BC=A0=E8=AF=81=E4=B9=A6=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20(#2735)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/1323 --- backend/app/api/v1/website_ssl.go | 21 +++ backend/app/dto/request/website_ssl.go | 8 ++ backend/app/service/website_ssl.go | 59 ++++++++ backend/router/ro_website_ssl.go | 1 + cmd/server/docs/docs.go | 86 ++++++++++-- cmd/server/docs/swagger.json | 82 +++++++++-- cmd/server/docs/swagger.yaml | 52 ++++++- frontend/src/api/interface/website.ts | 8 ++ frontend/src/api/modules/website.ts | 4 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/tw.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + .../src/views/website/ssl/detail/index.vue | 5 +- frontend/src/views/website/ssl/index.vue | 13 +- .../src/views/website/ssl/upload/index.vue | 132 ++++++++++++++++++ 15 files changed, 440 insertions(+), 34 deletions(-) create mode 100644 frontend/src/views/website/ssl/upload/index.vue diff --git a/backend/app/api/v1/website_ssl.go b/backend/app/api/v1/website_ssl.go index 0d1c33d3f..cd6439539 100644 --- a/backend/app/api/v1/website_ssl.go +++ b/backend/app/api/v1/website_ssl.go @@ -192,3 +192,24 @@ func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) { } helper.SuccessWithData(c, nil) } + +// @Tags Website SSL +// @Summary Upload ssl +// @Description 上传 ssl +// @Accept json +// @Param request body request.WebsiteSSLUpload true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/ssl/upload [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传 ssl [type]","formatEN":"Upload ssl [type]"} +func (b *BaseApi) UploadWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLUpload + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.Upload(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/request/website_ssl.go b/backend/app/dto/request/website_ssl.go index 13868f3c8..a6f979b84 100644 --- a/backend/app/dto/request/website_ssl.go +++ b/backend/app/dto/request/website_ssl.go @@ -50,3 +50,11 @@ type WebsiteSSLUpdate struct { ID uint `json:"id" validate:"required"` AutoRenew bool `json:"autoRenew" validate:"required"` } + +type WebsiteSSLUpload struct { + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + Type string `json:"type" validate:"required,oneof=paste local"` +} diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index c398eef05..a5a768990 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -11,6 +11,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/ssl" "path" "strconv" @@ -30,6 +31,7 @@ type IWebsiteSSLService interface { GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error) Delete(id uint) error Update(update request.WebsiteSSLUpdate) error + Upload(req request.WebsiteSSLUpload) error } func NewIWebsiteSSLService() IWebsiteSSLService { @@ -289,3 +291,60 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error { websiteSSL.AutoRenew = update.AutoRenew return websiteSSLRepo.Save(websiteSSL) } + +func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error { + newSSL := &model.WebsiteSSL{ + Provider: constant.Manual, + } + + if req.Type == "local" { + fileOp := files.NewFileOp() + if !fileOp.Stat(req.PrivateKeyPath) { + return buserr.New("ErrSSLKeyNotFound") + } + if !fileOp.Stat(req.CertificatePath) { + return buserr.New("ErrSSLCertificateNotFound") + } + if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil { + return err + } else { + newSSL.PrivateKey = string(content) + } + if content, err := fileOp.GetContent(req.CertificatePath); err != nil { + return err + } else { + newSSL.Pem = string(content) + } + } else { + newSSL.PrivateKey = req.PrivateKey + newSSL.Pem = req.Certificate + } + + privateKeyCertBlock, _ := pem.Decode([]byte(newSSL.PrivateKey)) + if privateKeyCertBlock == nil { + return buserr.New("ErrSSLKeyFormat") + } + + certBlock, _ := pem.Decode([]byte(newSSL.Pem)) + if certBlock == nil { + return buserr.New("ErrSSLCertificateFormat") + } + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return err + } + newSSL.ExpireDate = cert.NotAfter + newSSL.StartDate = cert.NotBefore + newSSL.Type = cert.Issuer.CommonName + if len(cert.Issuer.Organization) > 0 { + newSSL.Organization = cert.Issuer.Organization[0] + } else { + newSSL.Organization = cert.Issuer.CommonName + } + if len(cert.DNSNames) > 0 { + newSSL.PrimaryDomain = cert.DNSNames[0] + newSSL.Domains = strings.Join(cert.DNSNames, ",") + } + + return websiteSSLRepo.Create(context.Background(), newSSL) +} diff --git a/backend/router/ro_website_ssl.go b/backend/router/ro_website_ssl.go index 82e8a21ed..e126ab9cd 100644 --- a/backend/router/ro_website_ssl.go +++ b/backend/router/ro_website_ssl.go @@ -23,5 +23,6 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) { groupRouter.GET("/website/:websiteId", baseApi.GetWebsiteSSLByWebsiteId) groupRouter.GET("/:id", baseApi.GetWebsiteSSLById) groupRouter.POST("/update", baseApi.UpdateWebsiteSSL) + groupRouter.POST("/upload", baseApi.UploadWebsiteSSL) } } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index f3e52ae9c..07a693780 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag package docs import "github.com/swaggo/swag" @@ -12234,6 +12234,48 @@ const docTemplate = `{ } } }, + "/websites/ssl/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "上传 ssl", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Upload ssl", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl [type]", + "formatZH": "上传 ssl [type]", + "paramKeys": [] + } + } + }, "/websites/ssl/website/:websiteId": { "get": { "security": [ @@ -16861,19 +16903,10 @@ const docTemplate = `{ "type": "boolean" }, "sortBy": { - "type": "string", - "enum": [ - "name", - "size", - "modTime" - ] + "type": "string" }, "sortOrder": { - "type": "string", - "enum": [ - "ascending", - "descending" - ] + "type": "string" } } }, @@ -18160,6 +18193,33 @@ const docTemplate = `{ } } }, + "request.WebsiteSSLUpload": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "paste", + "local" + ] + } + } + }, "request.WebsiteSearch": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 30ae5bae8..5b31ce3a6 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -12227,6 +12227,48 @@ } } }, + "/websites/ssl/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "上传 ssl", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Upload ssl", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl [type]", + "formatZH": "上传 ssl [type]", + "paramKeys": [] + } + } + }, "/websites/ssl/website/:websiteId": { "get": { "security": [ @@ -16854,19 +16896,10 @@ "type": "boolean" }, "sortBy": { - "type": "string", - "enum": [ - "name", - "size", - "modTime" - ] + "type": "string" }, "sortOrder": { - "type": "string", - "enum": [ - "ascending", - "descending" - ] + "type": "string" } } }, @@ -18153,6 +18186,33 @@ } } }, + "request.WebsiteSSLUpload": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "paste", + "local" + ] + } + } + }, "request.WebsiteSearch": { "type": "object", "required": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 85a1bda72..437774acc 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2963,15 +2963,8 @@ definitions: showHidden: type: boolean sortBy: - enum: - - name - - size - - modTime type: string sortOrder: - enum: - - ascending - - descending type: string type: object request.FilePathCheck: @@ -3838,6 +3831,24 @@ definitions: - autoRenew - id type: object + request.WebsiteSSLUpload: + properties: + certificate: + type: string + certificatePath: + type: string + privateKey: + type: string + privateKeyPath: + type: string + type: + enum: + - paste + - local + type: string + required: + - type + type: object request.WebsiteSearch: properties: name: @@ -12080,6 +12091,33 @@ paths: formatEN: Update ssl config [domain] formatZH: 更新证书设置 [domain] paramKeys: [] + /websites/ssl/upload: + post: + consumes: + - application/json + description: 上传 ssl + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.WebsiteSSLUpload' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Upload ssl + tags: + - Website SSL + x-panel-log: + BeforeFunctions: [] + bodyKeys: + - type + formatEN: Upload ssl [type] + formatZH: 上传 ssl [type] + paramKeys: [] /websites/ssl/website/:websiteId: get: consumes: diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index c5e542751..11009f546 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -439,4 +439,12 @@ export namespace Website { userGroup: string; msg: string; } + + export interface SSLUpload { + privateKey: string; + certificate: string; + privateKeyPath: string; + certificatePath: string; + type: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index fadb2d18f..6162163ef 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -239,3 +239,7 @@ export const ChangePHPVersion = (req: Website.PHPVersionChange) => { export const GetDirConfig = (req: Website.ProxyReq) => { return http.post(`/websites/dir`, req); }; + +export const UploadSSL = (req: Website.SSLUpload) => { + return http.post(`/websites/ssl/upload`, req); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 88001f8f1..e776d435d 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1715,6 +1715,7 @@ const message = { 'This certificate has been associated with the following websites, and the renewal will be applied to these websites simultaneously', createAcme: 'Create Account', acmeHelper: 'Acme account is used to apply for free certificates', + upload: 'Upload Certificate', }, firewall: { create: 'Create rule', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index cfec83793..30308f8b6 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1626,6 +1626,7 @@ const message = { renewWebsite: '該證書已經和以下網站關聯,續簽會同步應用到這些網站', createAcme: '創建賬戶', acmeHelper: 'Acme 賬戶用於申請免費證書', + upload: '上傳證書', }, firewall: { create: '創建規則', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 247472e91..5bdc0791d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1626,6 +1626,7 @@ const message = { renewWebsite: '该证书已经和以下网站关联,续签会同步应用到这些网站', createAcme: '创建账户', acmeHelper: 'Acme 账户用于申请免费证书', + upload: '上传证书', }, firewall: { create: '创建规则', diff --git a/frontend/src/views/website/ssl/detail/index.vue b/frontend/src/views/website/ssl/detail/index.vue index 901d43e35..599d7f932 100644 --- a/frontend/src/views/website/ssl/detail/index.vue +++ b/frontend/src/views/website/ssl/detail/index.vue @@ -28,9 +28,12 @@ > {{ ssl.acmeAccount.email }} - + {{ ssl.type }} + + {{ ssl.organization }} + {{ dateFormatSimple(ssl.startDate) }} diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index ce42aa7d6..60705ceaa 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -13,6 +13,9 @@ {{ $t('ssl.create') }} + + {{ $t('ssl.upload') }} + {{ $t('website.acmeAccountManage') }} @@ -79,6 +82,7 @@ + @@ -99,6 +103,7 @@ import i18n from '@/lang'; import { Website } from '@/api/interface/website'; import { MsgSuccess } from '@/utils/message'; import { GlobalStore } from '@/store'; +import SSLUpload from './upload/index.vue'; const globalStore = GlobalStore(); const paginationConfig = reactive({ @@ -112,9 +117,10 @@ const dnsAccountRef = ref(); const sslCreateRef = ref(); const renewRef = ref(); const detailRef = ref(); -let data = ref(); -let loading = ref(false); +const data = ref(); +const loading = ref(false); const opRef = ref(); +const sslUploadRef = ref(); const routerButton = [ { @@ -187,6 +193,9 @@ const openDnsAccount = () => { const openSSL = () => { sslCreateRef.value.acceptParams(); }; +const openUpload = () => { + sslUploadRef.value.acceptParams(); +}; const openRenewSSL = (id: number, websites: Website.Website[]) => { renewRef.value.acceptParams({ id: id, websites: websites }); }; diff --git a/frontend/src/views/website/ssl/upload/index.vue b/frontend/src/views/website/ssl/upload/index.vue new file mode 100644 index 000000000..ad490640d --- /dev/null +++ b/frontend/src/views/website/ssl/upload/index.vue @@ -0,0 +1,132 @@ + + +