diff --git a/backend/app/api/v1/website_ssl.go b/backend/app/api/v1/website_ssl.go index 324d575da0b5..3584b5f576ac 100644 --- a/backend/app/api/v1/website_ssl.go +++ b/backend/app/api/v1/website_ssl.go @@ -1,7 +1,10 @@ package v1 import ( + "net/http" + "net/url" "reflect" + "strconv" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/dto" @@ -213,3 +216,32 @@ func (b *BaseApi) UploadWebsiteSSL(c *gin.Context) { } helper.SuccessWithData(c, nil) } + +// @Tags Website SSL +// @Summary Download SSL file +// @Description 下载证书文件 +// @Accept json +// @Param request body request.WebsiteResourceReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/ssl/download [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"下载证书文件 [domain]","formatEN":"download ssl file [domain]"} +func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) { + var req request.WebsiteResourceReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + file, err := websiteSSLService.DownloadFile(req.ID) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + info, err := file.Stat() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) +} diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 414f0aa73809..3c38b35aacff 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/app/repo" "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/i18n" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/files" @@ -41,6 +42,7 @@ type IWebsiteSSLService interface { Upload(req request.WebsiteSSLUpload) error ObtainSSL(apply request.WebsiteSSLApply) error SyncForRestart() error + DownloadFile(id uint) (*os.File, error) } func NewIWebsiteSSLService() IWebsiteSSLService { @@ -425,6 +427,34 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error { return websiteSSLRepo.Create(context.Background(), websiteSSL) } +func (w WebsiteSSLService) DownloadFile(id uint) (*os.File, error) { + websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(id)) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + dir := path.Join(global.CONF.System.BaseDir, "1panel/tmp/ssl", websiteSSL.PrimaryDomain) + if fileOp.Stat(dir) { + if err = fileOp.DeleteDir(dir); err != nil { + return nil, err + } + } + if err = fileOp.CreateDir(dir, 0666); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "fullchain.pem"), strings.NewReader(websiteSSL.Pem), 0644); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "privkey.pem"), strings.NewReader(websiteSSL.PrivateKey), 0644); err != nil { + return nil, err + } + fileName := websiteSSL.PrimaryDomain + ".zip" + if err = fileOp.Compress([]string{path.Join(dir, "fullchain.pem"), path.Join(dir, "privkey.pem")}, dir, fileName, files.SdkZip); err != nil { + return nil, err + } + return os.Open(path.Join(dir, fileName)) +} + func (w WebsiteSSLService) SyncForRestart() error { sslList, err := websiteSSLRepo.List() if err != nil { diff --git a/backend/router/ro_website_ssl.go b/backend/router/ro_website_ssl.go index 736e057ed721..8a01f87f03de 100644 --- a/backend/router/ro_website_ssl.go +++ b/backend/router/ro_website_ssl.go @@ -24,5 +24,6 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) { groupRouter.POST("/update", baseApi.UpdateWebsiteSSL) groupRouter.POST("/upload", baseApi.UploadWebsiteSSL) groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL) + groupRouter.POST("/download", baseApi.DownloadWebsiteSSL) } } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index e0bb76457bcb..e2a8524fd66b 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -12879,6 +12879,57 @@ const docTemplate = `{ } } }, + "/websites/ssl/download": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "下载证书文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Download SSL file", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ssl file [domain]", + "formatZH": "下载证书文件 [domain]", + "paramKeys": [] + } + } + }, "/websites/ssl/obtain": { "post": { "security": [ @@ -17478,6 +17529,9 @@ const docTemplate = `{ "createdAt": { "type": "string" }, + "description": { + "type": "string" + }, "dir": { "type": "string" }, @@ -19522,14 +19576,33 @@ const docTemplate = `{ "request.WebsiteSSLUpdate": { "type": "object", "required": [ - "id" + "id", + "type" ], "properties": { "autoRenew": { "type": "boolean" }, + "certificate": { + "type": "string" + }, + "description": { + "type": "string" + }, "id": { "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "autoRenew", + "description", + "certificate", + "privateKey" + ] } } }, @@ -19551,6 +19624,9 @@ const docTemplate = `{ "privateKeyPath": { "type": "string" }, + "sslID": { + "type": "integer" + }, "type": { "type": "string", "enum": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 0aa60ec20b04..f0609d65408d 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -12872,6 +12872,57 @@ } } }, + "/websites/ssl/download": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "下载证书文件", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Download SSL file", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ssl file [domain]", + "formatZH": "下载证书文件 [domain]", + "paramKeys": [] + } + } + }, "/websites/ssl/obtain": { "post": { "security": [ @@ -17471,6 +17522,9 @@ "createdAt": { "type": "string" }, + "description": { + "type": "string" + }, "dir": { "type": "string" }, @@ -19515,14 +19569,33 @@ "request.WebsiteSSLUpdate": { "type": "object", "required": [ - "id" + "id", + "type" ], "properties": { "autoRenew": { "type": "boolean" }, + "certificate": { + "type": "string" + }, + "description": { + "type": "string" + }, "id": { "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "autoRenew", + "description", + "certificate", + "privateKey" + ] } } }, @@ -19544,6 +19617,9 @@ "privateKeyPath": { "type": "string" }, + "sslID": { + "type": "integer" + }, "type": { "type": "string", "enum": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 15142f82931f..b42e30262fa6 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2800,6 +2800,8 @@ definitions: type: string createdAt: type: string + description: + type: string dir: type: string dnsAccount: @@ -4178,10 +4180,24 @@ definitions: properties: autoRenew: type: boolean + certificate: + type: string + description: + type: string id: type: integer + privateKey: + type: string + type: + enum: + - autoRenew + - description + - certificate + - privateKey + type: string required: - id + - type type: object request.WebsiteSSLUpload: properties: @@ -4193,6 +4209,8 @@ definitions: type: string privateKeyPath: type: string + sslID: + type: integer type: enum: - paste @@ -12874,6 +12892,39 @@ paths: formatEN: Delete ssl [domain] formatZH: 删除 ssl [domain] paramKeys: [] + /websites/ssl/download: + post: + consumes: + - application/json + description: 下载证书文件 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.WebsiteResourceReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Download SSL file + tags: + - Website SSL + x-panel-log: + BeforeFunctions: + - db: website_ssls + input_column: id + input_value: id + isList: false + output_column: primary_domain + output_value: domain + bodyKeys: + - id + formatEN: download ssl file [domain] + formatZH: 下载证书文件 [domain] + paramKeys: [] /websites/ssl/obtain: post: consumes: diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 0529a4733f98..493f6ae34323 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -490,4 +490,8 @@ export namespace Website { export interface RenewSSLByCA { SSLID: number; } + + export interface SSLDownload { + id: number; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 0a273bf42121..b5cdc366f686 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -263,3 +263,10 @@ export const DeleteCA = (req: Website.DelReq) => { export const RenewSSLByCA = (req: Website.RenewSSLByCA) => { return http.post(`/websites/ca/renew`, req); }; + +export const DownloadFile = (params: Website.SSLDownload) => { + return http.download(`/websites/ssl/download`, params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); +}; diff --git a/frontend/src/views/website/ssl/detail/index.vue b/frontend/src/views/website/ssl/detail/index.vue index 080104754d22..0e5178dcd694 100644 --- a/frontend/src/views/website/ssl/detail/index.vue +++ b/frontend/src/views/website/ssl/detail/index.vue @@ -3,15 +3,13 @@ -
+
{{ $t('ssl.msg') }} {{ $t('ssl.ssl') }} {{ $t('ssl.key') }} -
-
-
+
{{ ssl.primaryDomain }} @@ -52,14 +50,14 @@
-
+

{{ $t('file.copy') }}
-
+
({}); +const loading = ref(false); const handleClose = () => { open.value = false; diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index b8f75a6fcca0..cf71c26b8d3e 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -150,7 +150,7 @@