From 6c7e9d0a52e6058531be0515afa70b04341d8c4b Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:44:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=B5=81=E7=A8=8B=20(#2991)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/2429 Refs https://github.com/1Panel-dev/1Panel/issues/2861 Refs https://github.com/1Panel-dev/1Panel/issues/1948 --- backend/app/api/v1/website_ssl.go | 21 ++ backend/app/dto/request/website_ssl.go | 5 + backend/app/model/website_ssl.go | 3 + backend/app/service/website_ssl.go | 181 ++++++++++++------ backend/constant/status.go | 1 - backend/constant/website.go | 6 + backend/i18n/lang/en.yaml | 2 + backend/i18n/lang/zh-Hant.yaml | 1 + backend/i18n/lang/zh.yaml | 1 + backend/init/migration/migrate.go | 1 + backend/init/migration/migrations/v_1_9.go | 10 + backend/router/ro_website_ssl.go | 1 + cmd/server/docs/docs.go | 99 +++++++++- cmd/server/docs/swagger.json | 95 +++++++++ cmd/server/docs/swagger.yaml | 64 +++++++ frontend/src/api/interface/website.ts | 6 + frontend/src/api/modules/website.ts | 4 + frontend/src/components/status/index.vue | 1 + frontend/src/lang/modules/en.ts | 8 + frontend/src/lang/modules/tw.ts | 8 + frontend/src/lang/modules/zh.ts | 8 + .../src/views/website/ssl/apply/index.vue | 93 +++++++++ .../src/views/website/ssl/create/index.vue | 53 +---- .../src/views/website/ssl/detail/index.vue | 2 +- frontend/src/views/website/ssl/index.vue | 79 ++++++-- go.mod | 1 + go.sum | 27 +-- 27 files changed, 640 insertions(+), 141 deletions(-) create mode 100644 frontend/src/views/website/ssl/apply/index.vue diff --git a/backend/app/api/v1/website_ssl.go b/backend/app/api/v1/website_ssl.go index 2f981fe01..cf8ed2436 100644 --- a/backend/app/api/v1/website_ssl.go +++ b/backend/app/api/v1/website_ssl.go @@ -86,6 +86,27 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) { helper.SuccessWithData(c, nil) } +// @Tags Website SSL +// @Summary Apply ssl +// @Description 申请证书 +// @Accept json +// @Param request body request.WebsiteSSLApply true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/ssl/obtain [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":"apply ssl [domain]"} +func (b *BaseApi) ApplyWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLApply + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.ObtainSSL(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} + // @Tags Website SSL // @Summary Resolve website ssl // @Description 解析网站 ssl diff --git a/backend/app/dto/request/website_ssl.go b/backend/app/dto/request/website_ssl.go index fef59547f..3d201e929 100644 --- a/backend/app/dto/request/website_ssl.go +++ b/backend/app/dto/request/website_ssl.go @@ -15,6 +15,7 @@ type WebsiteSSLCreate struct { DnsAccountID uint `json:"dnsAccountId"` AutoRenew bool `json:"autoRenew"` KeyType string `json:"keyType"` + Apply bool `json:"apply"` } type WebsiteDNSReq struct { @@ -26,6 +27,10 @@ type WebsiteSSLRenew struct { SSLID uint `json:"SSLId" validate:"required"` } +type WebsiteSSLApply struct { + ID uint `json:"ID" validate:"required"` +} + type WebsiteAcmeAccountCreate struct { Email string `json:"email" validate:"required"` Type string `json:"type" validate:"required,oneof=letsencrypt zerossl buypass google"` diff --git a/backend/app/model/website_ssl.go b/backend/app/model/website_ssl.go index 7382fef04..7131ca0ef 100644 --- a/backend/app/model/website_ssl.go +++ b/backend/app/model/website_ssl.go @@ -17,6 +17,9 @@ type WebsiteSSL struct { AutoRenew bool `gorm:"type:varchar(64);not null" json:"autoRenew"` ExpireDate time.Time `json:"expireDate"` StartDate time.Time `json:"startDate"` + Status string `gorm:"not null;default:ready" json:"status"` + Message string `json:"message"` + KeyType string `gorm:"not null;default:2048" json:"keyType"` AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"` Websites []Website `json:"websites" gorm:"-:migration"` diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 103c69f99..7b8f2f69b 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -11,11 +11,15 @@ 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/common" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/ssl" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/jinzhu/gorm" "path" "strconv" "strings" + "time" ) type WebsiteSSLService struct { @@ -32,6 +36,7 @@ type IWebsiteSSLService interface { Delete(id uint) error Update(update request.WebsiteSSLUpdate) error Upload(req request.WebsiteSSLUpload) error + ObtainSSL(apply request.WebsiteSSLApply) error } func NewIWebsiteSSLService() IWebsiteSSLService { @@ -95,74 +100,39 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs if err != nil { return res, err } - client, err := ssl.NewAcmeClient(acmeAccount) - if err != nil { - return res, err + websiteSSL := model.WebsiteSSL{ + Status: constant.SSLInit, + Provider: create.Provider, + AcmeAccountID: acmeAccount.ID, + PrimaryDomain: create.PrimaryDomain, + ExpireDate: time.Now(), + KeyType: create.KeyType, } - var websiteSSL model.WebsiteSSL + var domains []string + if create.OtherDomains != "" { + otherDomainArray := strings.Split(create.OtherDomains, "\n") + for _, domain := range otherDomainArray { + if !common.IsValidDomain(domain) { + err = buserr.WithName("ErrDomainFormat", domain) + return res, err + } + domains = append(domains, domain) + } + } + websiteSSL.Domains = strings.Join(domains, ",") - switch create.Provider { - case constant.DNSAccount: + if create.Provider == constant.DNSAccount || create.Provider == constant.Http { + websiteSSL.AutoRenew = create.AutoRenew + } + if create.Provider == constant.DNSAccount { dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(create.DnsAccountID)) if err != nil { return res, err } - if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil { - return res, err - } - websiteSSL.AutoRenew = create.AutoRenew - case constant.Http: - appInstall, err := getAppInstallByKey(constant.AppOpenresty) - if err != nil { - return request.WebsiteSSLCreate{}, err - } - if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil { - return res, err - } - websiteSSL.AutoRenew = create.AutoRenew - case constant.DnsManual: - if err := client.UseManualDns(); err != nil { - return res, err - } + websiteSSL.DnsAccountID = dnsAccount.ID } - domains := []string{create.PrimaryDomain} - otherDomainArray := strings.Split(create.OtherDomains, "\n") - if create.OtherDomains != "" { - domains = append(otherDomainArray, domains...) - } - block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) - privateKey, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return res, err - } - - resource, err := client.ObtainSSL(domains, privateKey) - if err != nil { - return res, err - } - - if create.Provider == constant.DNSAccount { - websiteSSL.DnsAccountID = create.DnsAccountID - } - websiteSSL.AcmeAccountID = acmeAccount.ID - websiteSSL.Provider = create.Provider - websiteSSL.Domains = strings.Join(otherDomainArray, ",") - websiteSSL.PrimaryDomain = create.PrimaryDomain - websiteSSL.PrivateKey = string(resource.PrivateKey) - websiteSSL.Pem = string(resource.Certificate) - websiteSSL.CertURL = resource.CertURL - certBlock, _ := pem.Decode(resource.Certificate) - cert, err := x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return request.WebsiteSSLCreate{}, err - } - websiteSSL.ExpireDate = cert.NotAfter - websiteSSL.StartDate = cert.NotBefore - websiteSSL.Type = cert.Issuer.CommonName - websiteSSL.Organization = cert.Issuer.Organization[0] - if err := websiteSSLRepo.Create(context.TODO(), &websiteSSL); err != nil { return res, err } @@ -170,6 +140,99 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs return create, nil } +func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { + websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.ID)) + if err != nil { + return err + } + acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID)) + if err != nil { + return err + } + client, err := ssl.NewAcmeClient(acmeAccount) + if err != nil { + return err + } + + switch websiteSSL.Provider { + case constant.DNSAccount: + dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID)) + if err != nil { + return err + } + if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil { + return err + } + case constant.Http: + appInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + if gorm.IsRecordNotFoundError(err) { + return buserr.New("ErrOpenrestyNotFound") + } + return err + } + if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil { + return err + } + case constant.DnsManual: + if err := client.UseManualDns(); err != nil { + return err + } + } + + domains := []string{websiteSSL.PrimaryDomain} + domains = append(domains, domains...) + + privateKey, err := certcrypto.GeneratePrivateKey(ssl.KeyType(websiteSSL.KeyType)) + if err != nil { + return err + } + + websiteSSL.Status = constant.SSLApply + err = websiteSSLRepo.Save(websiteSSL) + if err != nil { + return err + } + + go func() { + resource, err := client.ObtainSSL(domains, privateKey) + if err != nil { + handleError(websiteSSL, err) + return + } + websiteSSL.PrivateKey = string(resource.PrivateKey) + websiteSSL.Pem = string(resource.Certificate) + websiteSSL.CertURL = resource.CertURL + certBlock, _ := pem.Decode(resource.Certificate) + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + handleError(websiteSSL, err) + return + } + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + websiteSSL.Organization = cert.Issuer.Organization[0] + websiteSSL.Status = constant.SSLReady + err = websiteSSLRepo.Save(websiteSSL) + if err != nil { + return + } + }() + + return nil +} + +func handleError(websiteSSL model.WebsiteSSL, err error) { + if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError { + websiteSSL.Status = constant.Error + } else { + websiteSSL.Status = constant.SSLApplyError + } + websiteSSL.Message = err.Error() + _ = websiteSSLRepo.Save(websiteSSL) +} + func (w WebsiteSSLService) Renew(sslId uint) error { websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(sslId)) if err != nil { diff --git a/backend/constant/status.go b/backend/constant/status.go index 7c248af27..161b50e00 100644 --- a/backend/constant/status.go +++ b/backend/constant/status.go @@ -3,7 +3,6 @@ package constant const ( StatusRunning = "Running" StatusDone = "Done" - StatusStoped = "Stoped" StatusWaiting = "Waiting" StatusSuccess = "Success" StatusFailed = "Failed" diff --git a/backend/constant/website.go b/backend/constant/website.go index 6392fd4fe..fbad8a6ec 100644 --- a/backend/constant/website.go +++ b/backend/constant/website.go @@ -45,4 +45,10 @@ const ( ConfigPHP = "php" ConfigFPM = "fpm" + + SSLInit = "init" + SSLError = "error" + SSLReady = "ready" + SSLApply = "applying" + SSLApplyError = "applyError" ) diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 01f40b34e..c69445731 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -92,6 +92,8 @@ ErrSSLCertificateNotFound: 'The certificate file does not exist' ErrSSLKeyFormat: 'Private key file verification error' ErrSSLCertificateFormat: 'Certificate file format error, please use pem format' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be empty' +ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first' + #mysql ErrUserIsExist: "The current user already exists. Please enter a new user" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 87de11519..ebf43eba8 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -92,6 +92,7 @@ ErrSSLCertificateNotFound: '證書文件不存在' ErrSSLKeyFormat: '私鑰文件校驗錯誤' ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空' +ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty' #mysql ErrUserIsExist: "當前用戶已存在,請重新輸入" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 3a3583dfc..c6e9be84d 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -92,6 +92,7 @@ ErrSSLCertificateNotFound: '证书文件不存在' ErrSSLKeyFormat: '私钥文件校验失败' ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式' ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空' +ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty' #mysql ErrUserIsExist: "当前用户已存在,请重新输入" diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 1f0d1f635..57da7f746 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -54,6 +54,7 @@ func Init() { migrations.AddAppSyncStatus, migrations.UpdateAcmeAccount, + migrations.UpdateWebsiteSSL, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_9.go b/backend/init/migration/migrations/v_1_9.go index 3c2236b04..cb74a944d 100644 --- a/backend/init/migration/migrations/v_1_9.go +++ b/backend/init/migration/migrations/v_1_9.go @@ -15,3 +15,13 @@ var UpdateAcmeAccount = &gormigrate.Migration{ return nil }, } + +var UpdateWebsiteSSL = &gormigrate.Migration{ + ID: "20231119-update-website-ssl", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil { + return err + } + return nil + }, +} diff --git a/backend/router/ro_website_ssl.go b/backend/router/ro_website_ssl.go index e126ab9cd..d34c74f58 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.GET("/:id", baseApi.GetWebsiteSSLById) groupRouter.POST("/update", baseApi.UpdateWebsiteSSL) groupRouter.POST("/upload", baseApi.UploadWebsiteSSL) + groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL) } } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 4ef8a06df..824afeecf 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" @@ -12349,6 +12349,57 @@ const docTemplate = `{ } } }, + "/websites/ssl/obtain": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "申请证书", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Apply ssl", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLApply" + } + } + ], + "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": "apply ssl [domain]", + "formatZH": "申请证书 [domain]", + "paramKeys": [] + } + } + }, "/websites/ssl/renew": { "post": { "security": [ @@ -16736,6 +16787,9 @@ const docTemplate = `{ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, "type": { "type": "string" }, @@ -16800,6 +16854,12 @@ const docTemplate = `{ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, + "message": { + "type": "string" + }, "organization": { "type": "string" }, @@ -16818,6 +16878,9 @@ const docTemplate = `{ "startDate": { "type": "string" }, + "status": { + "type": "string" + }, "type": { "type": "string" }, @@ -18113,6 +18176,7 @@ const docTemplate = `{ "type": "object", "required": [ "email", + "keyType", "type" ], "properties": { @@ -18125,6 +18189,17 @@ const docTemplate = `{ "email": { "type": "string" }, + "keyType": { + "type": "string", + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ] + }, "type": { "type": "string", "enum": [ @@ -18597,6 +18672,17 @@ const docTemplate = `{ } } }, + "request.WebsiteSSLApply": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "integer" + } + } + }, "request.WebsiteSSLCreate": { "type": "object", "required": [ @@ -18608,12 +18694,18 @@ const docTemplate = `{ "acmeAccountId": { "type": "integer" }, + "apply": { + "type": "boolean" + }, "autoRenew": { "type": "boolean" }, "dnsAccountId": { "type": "integer" }, + "keyType": { + "type": "string" + }, "otherDomains": { "type": "string" }, @@ -19231,6 +19323,9 @@ const docTemplate = `{ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, "type": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 4228d152e..4570e39b6 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -12342,6 +12342,57 @@ } } }, + "/websites/ssl/obtain": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "申请证书", + "consumes": [ + "application/json" + ], + "tags": [ + "Website SSL" + ], + "summary": "Apply ssl", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLApply" + } + } + ], + "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": "apply ssl [domain]", + "formatZH": "申请证书 [domain]", + "paramKeys": [] + } + } + }, "/websites/ssl/renew": { "post": { "security": [ @@ -16729,6 +16780,9 @@ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, "type": { "type": "string" }, @@ -16793,6 +16847,12 @@ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, + "message": { + "type": "string" + }, "organization": { "type": "string" }, @@ -16811,6 +16871,9 @@ "startDate": { "type": "string" }, + "status": { + "type": "string" + }, "type": { "type": "string" }, @@ -18106,6 +18169,7 @@ "type": "object", "required": [ "email", + "keyType", "type" ], "properties": { @@ -18118,6 +18182,17 @@ "email": { "type": "string" }, + "keyType": { + "type": "string", + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ] + }, "type": { "type": "string", "enum": [ @@ -18590,6 +18665,17 @@ } } }, + "request.WebsiteSSLApply": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "integer" + } + } + }, "request.WebsiteSSLCreate": { "type": "object", "required": [ @@ -18601,12 +18687,18 @@ "acmeAccountId": { "type": "integer" }, + "apply": { + "type": "boolean" + }, "autoRenew": { "type": "boolean" }, "dnsAccountId": { "type": "integer" }, + "keyType": { + "type": "string" + }, "otherDomains": { "type": "string" }, @@ -19224,6 +19316,9 @@ "id": { "type": "integer" }, + "keyType": { + "type": "string" + }, "type": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 4171f3ddd..dddad1fde 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -2661,6 +2661,8 @@ definitions: type: string id: type: integer + keyType: + type: string type: type: string updatedAt: @@ -2703,6 +2705,10 @@ definitions: type: string id: type: integer + keyType: + type: string + message: + type: string organization: type: string pem: @@ -2715,6 +2721,8 @@ definitions: type: string startDate: type: string + status: + type: string type: type: string updatedAt: @@ -3586,6 +3594,15 @@ definitions: type: string email: type: string + keyType: + enum: + - P256 + - P384 + - "2048" + - "3072" + - "4096" + - "8192" + type: string type: enum: - letsencrypt @@ -3595,6 +3612,7 @@ definitions: type: string required: - email + - keyType - type type: object request.WebsiteCommonReq: @@ -3907,14 +3925,25 @@ definitions: required: - id type: object + request.WebsiteSSLApply: + properties: + ID: + type: integer + required: + - ID + type: object request.WebsiteSSLCreate: properties: acmeAccountId: type: integer + apply: + type: boolean autoRenew: type: boolean dnsAccountId: type: integer + keyType: + type: string otherDomains: type: string primaryDomain: @@ -4328,6 +4357,8 @@ definitions: type: string id: type: integer + keyType: + type: string type: type: string updatedAt: @@ -12291,6 +12322,39 @@ paths: formatEN: Delete ssl [domain] formatZH: 删除 ssl [domain] paramKeys: [] + /websites/ssl/obtain: + post: + consumes: + - application/json + description: 申请证书 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.WebsiteSSLApply' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Apply ssl + 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: apply ssl [domain] + formatZH: 申请证书 [domain] + paramKeys: [] /websites/ssl/renew: post: consumes: diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index f6d14f201..e5951d573 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -164,6 +164,8 @@ export namespace Website { websites?: Website.Website[]; autoRenew: boolean; acmeAccountId?: number; + status: string; + domains?: string; } export interface SSLCreate { @@ -446,4 +448,8 @@ export namespace Website { certificatePath: string; type: string; } + + export interface SSLObtain { + ID: number; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index b49649dbc..de6b3d8f9 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -120,6 +120,10 @@ export const ApplySSL = (req: Website.SSLApply) => { return http.post(`/websites/ssl/apply`, req); }; +export const ObtainSSL = (req: Website.SSLObtain) => { + return http.post(`/websites/ssl/obtain`, req); +}; + export const RenewSSL = (req: Website.SSLRenew) => { return http.post(`/websites/ssl/renew`, req, TimeoutEnum.T_10M); }; diff --git a/frontend/src/components/status/index.vue b/frontend/src/components/status/index.vue index da1c2eb47..36b128201 100644 --- a/frontend/src/components/status/index.vue +++ b/frontend/src/components/status/index.vue @@ -48,6 +48,7 @@ const loadingStatus = [ 'creating', 'starting', 'removing', + 'applying', ]; const loadingIcon = (status: string): boolean => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 9e996ba20..626a34432 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -236,6 +236,10 @@ const message = { recreating: 'Recreating', creating: 'Creating', systemrestart: 'Interrupt', + init: 'Waiting for application', + ready: 'normal', + applying: 'Applying', + applyerror: 'Failure', }, units: { second: 'Second', @@ -1753,6 +1757,10 @@ const message = { createAcme: 'Create Account', acmeHelper: 'Acme account is used to apply for free certificates', upload: 'Upload Certificate', + applyType: 'Application method', + apply: 'Apply', + applyStart: 'Certificate application starts', + getDnsResolve: 'Getting DNS resolution value, please wait...', }, firewall: { create: 'Create rule', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index fa3432c52..60c4b6c6d 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -235,6 +235,10 @@ const message = { recreating: '重建中', creating: '創建中', systemrestart: '中斷', + init: '等待申請', + ready: '正常', + applying: '申請中', + applyerror: '失敗', }, units: { second: '秒', @@ -1664,6 +1668,10 @@ const message = { createAcme: '創建賬戶', acmeHelper: 'Acme 賬戶用於申請免費證書', upload: '上傳證書', + applyType: '申請方式', + apply: '申請', + applyStart: '證書申請開始', + getDnsResolve: '正在取得 DNS 解析值,請稍後 ...', }, firewall: { create: '創建規則', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 49b892137..e74ee472b 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -235,6 +235,10 @@ const message = { recreating: '重建中', creating: '创建中', systemrestart: '中断', + init: '等待申请', + ready: '正常', + applying: '申请中', + applyerror: '失败', }, units: { second: '秒', @@ -1664,6 +1668,10 @@ const message = { createAcme: '创建账户', acmeHelper: 'Acme 账户用于申请免费证书', upload: '上传证书', + applyType: '申请方式', + apply: '申请', + applyStart: '证书申请开始', + getDnsResolve: '正在获取 DNS 解析值,请稍后 ...', }, firewall: { create: '创建规则', diff --git a/frontend/src/views/website/ssl/apply/index.vue b/frontend/src/views/website/ssl/apply/index.vue new file mode 100644 index 000000000..72d28c100 --- /dev/null +++ b/frontend/src/views/website/ssl/apply/index.vue @@ -0,0 +1,93 @@ + + + diff --git a/frontend/src/views/website/ssl/create/index.vue b/frontend/src/views/website/ssl/create/index.vue index 61d1cf63e..1502e2b40 100644 --- a/frontend/src/views/website/ssl/create/index.vue +++ b/frontend/src/views/website/ssl/create/index.vue @@ -57,15 +57,6 @@ > - - {{ $t('ssl.dnsResolveHelper') }} - - - - - TXT - - @@ -86,7 +77,7 @@