1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +08:00

feat: 增加证书推送到本地目录功能 (#3070)

This commit is contained in:
zhengkunwang 2023-11-27 14:00:09 +08:00 committed by GitHub
parent 9cd7f0d681
commit fdb6242d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 270 additions and 47 deletions

View File

@ -16,6 +16,8 @@ type WebsiteSSLCreate struct {
AutoRenew bool `json:"autoRenew"` AutoRenew bool `json:"autoRenew"`
KeyType string `json:"keyType"` KeyType string `json:"keyType"`
Apply bool `json:"apply"` Apply bool `json:"apply"`
PushDir bool `json:"pushDir"`
Dir string `json:"dir"`
} }
type WebsiteDNSReq struct { type WebsiteDNSReq struct {
@ -91,4 +93,6 @@ type WebsiteCAObtain struct {
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
Time int `json:"time" validate:"required"` Time int `json:"time" validate:"required"`
Unit string `json:"unit" validate:"required"` Unit string `json:"unit" validate:"required"`
PushDir bool `json:"pushDir"`
Dir string `json:"dir"`
} }

View File

@ -25,8 +25,11 @@ type WebsiteSSL struct {
Status string `gorm:"not null;default:ready" json:"status"` Status string `gorm:"not null;default:ready" json:"status"`
Message string `json:"message"` Message string `json:"message"`
KeyType string `gorm:"not null;default:2048" json:"keyType"` KeyType string `gorm:"not null;default:2048" json:"keyType"`
PushDir bool `gorm:"not null;default:0" json:"pushDir"`
Dir string `json:"dir"`
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"` AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
Websites []Website `json:"websites" gorm:"-:migration"` Websites []Website `json:"websites" gorm:"-:migration"`
} }

View File

@ -48,14 +48,14 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W
db := getDb(opts...).Model(&model.WebsiteSSL{}) db := getDb(opts...).Model(&model.WebsiteSSL{})
count := int64(0) count := int64(0)
db = db.Count(&count) db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Preload("AcmeAccount").Preload("Websites").Find(&sslList).Error err := db.Limit(size).Offset(size * (page - 1)).Preload("AcmeAccount").Preload("DnsAccount").Preload("Websites").Find(&sslList).Error
return count, sslList, err return count, sslList, err
} }
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (model.WebsiteSSL, error) { func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (model.WebsiteSSL, error) {
var website model.WebsiteSSL var website model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{}) db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").First(&website).Error; err != nil { if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil {
return website, err return website, err
} }
return website, nil return website, nil
@ -64,7 +64,7 @@ func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (model.WebsiteSSL, error) {
func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) { func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) {
var websites []model.WebsiteSSL var websites []model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{}) db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").Find(&websites).Error; err != nil { if err := db.Preload("AcmeAccount").Preload("DnsAccount").Find(&websites).Error; err != nil {
return websites, err return websites, err
} }
return websites, nil return websites, nil

View File

@ -9,16 +9,22 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/common" "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/1Panel-dev/1Panel/backend/utils/ssl"
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certcrypto"
"log"
"math/big" "math/big"
"net" "net"
"os"
"path"
"strings" "strings"
"time" "time"
) )
@ -154,6 +160,13 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
newSSL := &model.WebsiteSSL{ newSSL := &model.WebsiteSSL{
Provider: constant.SelfSigned, Provider: constant.SelfSigned,
KeyType: req.KeyType, KeyType: req.KeyType,
PushDir: req.PushDir,
}
if req.PushDir {
if !files.NewFileOp().Stat(req.Dir) {
return buserr.New(constant.ErrLinkPathNotFound)
}
newSSL.Dir = req.Dir
} }
var ( var (
@ -274,7 +287,15 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
newSSL.Type = cert.Issuer.CommonName newSSL.Type = cert.Issuer.CommonName
newSSL.Organization = rootCsr.Subject.Organization[0] newSSL.Organization = rootCsr.Subject.Organization[0]
return websiteSSLRepo.Create(context.Background(), newSSL) if err := websiteSSLRepo.Create(context.Background(), newSSL); err != nil {
return err
}
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", newSSL.PrimaryDomain, newSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
saveCertificateFile(*newSSL, logger)
return nil
} }
func createPrivateKey(keyType string) (privateKey any, publicKey any, privateKeyBytes []byte, err error) { func createPrivateKey(keyType string) (privateKey any, publicKey any, privateKeyBytes []byte, err error) {

View File

@ -114,6 +114,13 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
PrimaryDomain: create.PrimaryDomain, PrimaryDomain: create.PrimaryDomain,
ExpireDate: time.Now(), ExpireDate: time.Now(),
KeyType: create.KeyType, KeyType: create.KeyType,
PushDir: create.PushDir,
}
if create.PushDir {
if !files.NewFileOp().Stat(create.Dir) {
return res, buserr.New(constant.ErrLinkPathNotFound)
}
websiteSSL.Dir = create.Dir
} }
var domains []string var domains []string
@ -240,6 +247,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Organization = cert.Issuer.Organization[0] websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady websiteSSL.Status = constant.SSLReady
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
saveCertificateFile(websiteSSL, logger)
err = websiteSSLRepo.Save(websiteSSL) err = websiteSSLRepo.Save(websiteSSL)
if err != nil { if err != nil {
return return

View File

@ -8,6 +8,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components" "github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"log"
"path" "path"
"reflect" "reflect"
"strconv" "strconv"
@ -748,3 +749,26 @@ func getWebsiteDomains(domains string, defaultPort int, websiteID uint) (domainM
return return
} }
func saveCertificateFile(websiteSSL model.WebsiteSSL, logger *log.Logger) {
if websiteSSL.PushDir {
fileOp := files.NewFileOp()
var (
pushErr error
MsgMap = map[string]interface{}{"path": websiteSSL.Dir, "status": i18n.GetMsgByKey("Success")}
)
if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "privkey.pem"), websiteSSL.PrivateKey, 0666); pushErr != nil {
MsgMap["status"] = i18n.GetMsgByKey("Failed")
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
logger.Println("Push dir failed:" + pushErr.Error())
}
if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "fullchain.pem"), websiteSSL.Pem, 0666); pushErr != nil {
MsgMap["status"] = i18n.GetMsgByKey("Failed")
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
logger.Println("Push dir failed:" + pushErr.Error())
}
if pushErr == nil {
logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap))
}
}
}

View File

@ -20,6 +20,8 @@ TYPE_RUNTIME: "Runtime environment"
TYPE_DOMAIN: "Domain name" TYPE_DOMAIN: "Domain name"
ErrTypePort: 'Port {{ .name }} format error' ErrTypePort: 'Port {{ .name }} format error'
ErrTypePortRange: 'Port range needs to be between 1-65535' ErrTypePortRange: 'Port range needs to be between 1-65535'
Success: "Success"
Failed: "Failed"
#app #app
ErrPortInUsed: "{{ .detail }} port already in use" ErrPortInUsed: "{{ .detail }} port already in use"
@ -100,7 +102,7 @@ http: "HTTP"
ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} ' ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} '
ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! ' ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! '
DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]' DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]'
PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}'
#mysql #mysql
ErrUserIsExist: "The current user already exists. Please enter a new user" ErrUserIsExist: "The current user already exists. Please enter a new user"

View File

@ -20,6 +20,8 @@ TYPE_RUNTIME: "運作環境"
TYPE_DOMAIN: "網域名稱" TYPE_DOMAIN: "網域名稱"
ErrTypePort: '埠 {{ .name }} 格式錯誤' ErrTypePort: '埠 {{ .name }} 格式錯誤'
ErrTypePortRange: '連接埠範圍需要在 1-65535 之間' ErrTypePortRange: '連接埠範圍需要在 1-65535 之間'
Success: "成功"
Failed: "失敗"
#app #app
ErrPortInUsed: "{{ .detail }} 端口已被佔用!" ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
@ -100,6 +102,8 @@ http: "HTTP"
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} ' ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! ' ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! '
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]' DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}'
#mysql #mysql
ErrUserIsExist: "當前用戶已存在,請重新輸入" ErrUserIsExist: "當前用戶已存在,請重新輸入"

View File

@ -20,6 +20,8 @@ TYPE_RUNTIME: "运行环境"
TYPE_DOMAIN: "域名" TYPE_DOMAIN: "域名"
ErrTypePort: '端口 {{ .name }} 格式错误' ErrTypePort: '端口 {{ .name }} 格式错误'
ErrTypePortRange: '端口范围需要在 1-65535 之间' ErrTypePortRange: '端口范围需要在 1-65535 之间'
Success: "成功"
Failed: "失败"
#app #app
ErrPortInUsed: "{{ .detail }} 端口已被占用!" ErrPortInUsed: "{{ .detail }} 端口已被占用!"
@ -100,6 +102,7 @@ http: "HTTP"
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} ' ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!' ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]' DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}'
#mysql #mysql
ErrUserIsExist: "当前用户已存在,请重新输入" ErrUserIsExist: "当前用户已存在,请重新输入"

View File

@ -16,16 +16,6 @@ var UpdateAcmeAccount = &gormigrate.Migration{
}, },
} }
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
},
}
var AddWebsiteCA = &gormigrate.Migration{ var AddWebsiteCA = &gormigrate.Migration{
ID: "20231125-add-website-ca", ID: "20231125-add-website-ca",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
@ -35,3 +25,13 @@ var AddWebsiteCA = &gormigrate.Migration{
return nil return nil
}, },
} }
var UpdateWebsiteSSL = &gormigrate.Migration{
ID: "20231126-update-website-ssl",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil {
return err
}
return nil
},
}

View File

@ -16934,17 +16934,11 @@ const docTemplate = `{
"dto.SwapHelper": { "dto.SwapHelper": {
"type": "object", "type": "object",
"required": [ "required": [
"operate",
"path" "path"
], ],
"properties": { "properties": {
"operate": { "isNew": {
"type": "string", "type": "boolean"
"enum": [
"create",
"delete",
"update"
]
}, },
"path": { "path": {
"type": "string" "type": "string"
@ -17468,6 +17462,26 @@ const docTemplate = `{
} }
} }
}, },
"model.WebsiteDnsAccount": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"model.WebsiteDomain": { "model.WebsiteDomain": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -17509,6 +17523,12 @@ const docTemplate = `{
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
"dir": {
"type": "string"
},
"dnsAccount": {
"$ref": "#/definitions/model.WebsiteDnsAccount"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -17542,6 +17562,9 @@ const docTemplate = `{
"provider": { "provider": {
"type": "string" "type": "string"
}, },
"pushDir": {
"type": "boolean"
},
"startDate": { "startDate": {
"type": "string" "type": "string"
}, },
@ -18942,6 +18965,9 @@ const docTemplate = `{
"unit" "unit"
], ],
"properties": { "properties": {
"dir": {
"type": "string"
},
"domains": { "domains": {
"type": "string" "type": "string"
}, },
@ -18959,6 +18985,9 @@ const docTemplate = `{
"8192" "8192"
] ]
}, },
"pushDir": {
"type": "boolean"
},
"time": { "time": {
"type": "integer" "type": "integer"
}, },
@ -19471,6 +19500,9 @@ const docTemplate = `{
"autoRenew": { "autoRenew": {
"type": "boolean" "type": "boolean"
}, },
"dir": {
"type": "string"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -19485,6 +19517,9 @@ const docTemplate = `{
}, },
"provider": { "provider": {
"type": "string" "type": "string"
},
"pushDir": {
"type": "boolean"
} }
} }
}, },

View File

@ -16927,17 +16927,11 @@
"dto.SwapHelper": { "dto.SwapHelper": {
"type": "object", "type": "object",
"required": [ "required": [
"operate",
"path" "path"
], ],
"properties": { "properties": {
"operate": { "isNew": {
"type": "string", "type": "boolean"
"enum": [
"create",
"delete",
"update"
]
}, },
"path": { "path": {
"type": "string" "type": "string"
@ -17461,6 +17455,26 @@
} }
} }
}, },
"model.WebsiteDnsAccount": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"model.WebsiteDomain": { "model.WebsiteDomain": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -17502,6 +17516,12 @@
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
"dir": {
"type": "string"
},
"dnsAccount": {
"$ref": "#/definitions/model.WebsiteDnsAccount"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -17535,6 +17555,9 @@
"provider": { "provider": {
"type": "string" "type": "string"
}, },
"pushDir": {
"type": "boolean"
},
"startDate": { "startDate": {
"type": "string" "type": "string"
}, },
@ -18935,6 +18958,9 @@
"unit" "unit"
], ],
"properties": { "properties": {
"dir": {
"type": "string"
},
"domains": { "domains": {
"type": "string" "type": "string"
}, },
@ -18952,6 +18978,9 @@
"8192" "8192"
] ]
}, },
"pushDir": {
"type": "boolean"
},
"time": { "time": {
"type": "integer" "type": "integer"
}, },
@ -19464,6 +19493,9 @@
"autoRenew": { "autoRenew": {
"type": "boolean" "type": "boolean"
}, },
"dir": {
"type": "string"
},
"dnsAccountId": { "dnsAccountId": {
"type": "integer" "type": "integer"
}, },
@ -19478,6 +19510,9 @@
}, },
"provider": { "provider": {
"type": "string" "type": "string"
},
"pushDir": {
"type": "boolean"
} }
} }
}, },

View File

@ -2409,12 +2409,8 @@ definitions:
type: object type: object
dto.SwapHelper: dto.SwapHelper:
properties: properties:
operate: isNew:
enum: type: boolean
- create
- delete
- update
type: string
path: path:
type: string type: string
size: size:
@ -2422,7 +2418,6 @@ definitions:
used: used:
type: string type: string
required: required:
- operate
- path - path
type: object type: object
dto.TreeChild: dto.TreeChild:
@ -2761,6 +2756,19 @@ definitions:
url: url:
type: string type: string
type: object type: object
model.WebsiteDnsAccount:
properties:
createdAt:
type: string
id:
type: integer
name:
type: string
type:
type: string
updatedAt:
type: string
type: object
model.WebsiteDomain: model.WebsiteDomain:
properties: properties:
createdAt: createdAt:
@ -2788,6 +2796,10 @@ definitions:
type: string type: string
createdAt: createdAt:
type: string type: string
dir:
type: string
dnsAccount:
$ref: '#/definitions/model.WebsiteDnsAccount'
dnsAccountId: dnsAccountId:
type: integer type: integer
domains: domains:
@ -2810,6 +2822,8 @@ definitions:
type: string type: string
provider: provider:
type: string type: string
pushDir:
type: boolean
startDate: startDate:
type: string type: string
status: status:
@ -3747,6 +3761,8 @@ definitions:
type: object type: object
request.WebsiteCAObtain: request.WebsiteCAObtain:
properties: properties:
dir:
type: string
domains: domains:
type: string type: string
id: id:
@ -3760,6 +3776,8 @@ definitions:
- "4096" - "4096"
- "8192" - "8192"
type: string type: string
pushDir:
type: boolean
time: time:
type: integer type: integer
unit: unit:
@ -4106,6 +4124,8 @@ definitions:
type: boolean type: boolean
autoRenew: autoRenew:
type: boolean type: boolean
dir:
type: string
dnsAccountId: dnsAccountId:
type: integer type: integer
keyType: keyType:
@ -4116,6 +4136,8 @@ definitions:
type: string type: string
provider: provider:
type: string type: string
pushDir:
type: boolean
required: required:
- acmeAccountId - acmeAccountId
- primaryDomain - primaryDomain

View File

@ -482,5 +482,7 @@ export namespace Website {
keyType: string; keyType: string;
time: number; time: number;
unit: string; unit: string;
pushDir: boolean;
dir: string;
} }
} }

View File

@ -1836,6 +1836,10 @@ const message = {
selfSign: 'Issue certificate', selfSign: 'Issue certificate',
days: 'validity period', days: 'validity period',
domainHelper: 'One domain name per line, supports * and IP address', domainHelper: 'One domain name per line, supports * and IP address',
pushDir: 'Push the certificate to the local directory',
dir: 'directory',
pushDirHelper:
'Two files will be generated in this directory, the certificate file: fullchain.pem and the key file: privkey.pem',
}, },
firewall: { firewall: {
create: 'Create rule', create: 'Create rule',

View File

@ -1724,6 +1724,9 @@ const message = {
selfSign: '簽發證書', selfSign: '簽發證書',
days: '有效期限', days: '有效期限',
domainHelper: '一行一個網域名稱,支援*和IP位址', domainHelper: '一行一個網域名稱,支援*和IP位址',
pushDir: '推送憑證到本機目錄',
dir: '目錄',
pushDirHelper: '會在此目錄下產生兩個文件憑證檔案fullchain.pem 金鑰檔案privkey.pem',
}, },
firewall: { firewall: {
create: '創建規則', create: '創建規則',

View File

@ -1724,6 +1724,9 @@ const message = {
selfSign: '签发证书', selfSign: '签发证书',
days: '有效期', days: '有效期',
domainHelper: '一行一个域名,支持*和IP地址', domainHelper: '一行一个域名,支持*和IP地址',
pushDir: '推送证书到本地目录',
dir: '目录',
pushDirHelper: '会在此目录下生成两个文件证书文件fullchain.pem 密钥文件privkey.pem',
}, },
firewall: { firewall: {
create: '创建规则', create: '创建规则',

View File

@ -37,6 +37,19 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="''" prop="pushDir">
<el-checkbox v-model="obtain.pushDir" :label="$t('ssl.pushDir')" />
</el-form-item>
<el-form-item :label="$t('ssl.dir')" prop="dir" v-if="obtain.pushDir">
<el-input v-model.trim="obtain.dir">
<template #prepend>
<FileList :path="obtain.dir" @choose="getPath" :dir="true"></FileList>
</template>
</el-input>
<span class="input-help">
{{ $t('ssl.pushDirHelper') }}
</span>
</el-form-item>
</el-form> </el-form>
</el-col> </el-col>
</el-row> </el-row>
@ -69,6 +82,7 @@ const rules = ref({
keyType: [Rules.requiredSelect], keyType: [Rules.requiredSelect],
domains: [Rules.requiredInput], domains: [Rules.requiredInput],
time: [Rules.requiredInput, checkNumberRange(1, 1000)], time: [Rules.requiredInput, checkNumberRange(1, 1000)],
dir: [Rules.requiredInput],
}); });
const initData = () => ({ const initData = () => ({
@ -77,6 +91,8 @@ const initData = () => ({
id: 0, id: 0,
time: 0, time: 0,
unit: 'day', unit: 'day',
pushDir: false,
dir: '',
}); });
const obtain = ref(initData()); const obtain = ref(initData());
@ -96,6 +112,10 @@ const resetForm = () => {
obtain.value = initData(); obtain.value = initData();
}; };
const getPath = (dir: string) => {
obtain.value.dir = dir;
};
const submit = async (formEl: FormInstance | undefined) => { const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
await formEl.validate((valid) => { await formEl.validate((valid) => {

View File

@ -71,6 +71,19 @@
<el-form-item :label="''" prop="autoRenew" v-if="ssl.provider !== 'dnsManual'"> <el-form-item :label="''" prop="autoRenew" v-if="ssl.provider !== 'dnsManual'">
<el-checkbox v-model="ssl.autoRenew" :label="$t('ssl.autoRenew')" /> <el-checkbox v-model="ssl.autoRenew" :label="$t('ssl.autoRenew')" />
</el-form-item> </el-form-item>
<el-form-item :label="''" prop="pushDir">
<el-checkbox v-model="ssl.pushDir" :label="$t('ssl.pushDir')" />
</el-form-item>
<el-form-item :label="$t('ssl.dir')" prop="dir" v-if="ssl.pushDir">
<el-input v-model.trim="ssl.dir">
<template #prepend>
<FileList :path="ssl.dir" @choose="getPath" :dir="true"></FileList>
</template>
</el-input>
<span class="input-help">
{{ $t('ssl.pushDirHelper') }}
</span>
</el-form-item>
</el-form> </el-form>
</el-col> </el-col>
</el-row> </el-row>
@ -127,6 +140,7 @@ const rules = ref({
provider: [Rules.requiredInput], provider: [Rules.requiredInput],
autoRenew: [Rules.requiredInput], autoRenew: [Rules.requiredInput],
keyType: [Rules.requiredInput], keyType: [Rules.requiredInput],
dir: [Rules.requiredInput],
}); });
const initData = () => ({ const initData = () => ({
@ -138,6 +152,8 @@ const initData = () => ({
dnsAccountId: undefined, dnsAccountId: undefined,
autoRenew: true, autoRenew: true,
keyType: 'P256', keyType: 'P256',
pushDir: false,
dir: '',
}); });
const ssl = ref(initData()); const ssl = ref(initData());
@ -163,6 +179,10 @@ const acceptParams = () => {
open.value = true; open.value = true;
}; };
const getPath = (dir: string) => {
ssl.value.dir = dir;
};
const getAcmeAccounts = async () => { const getAcmeAccounts = async () => {
const res = await SearchAcmeAccount(acmeReq); const res = await SearchAcmeAccount(acmeReq);
acmeAccounts.value = res.data.items || []; acmeAccounts.value = res.data.items || [];

View File

@ -19,15 +19,6 @@
<el-descriptions-item :label="$t('website.otherDomains')"> <el-descriptions-item :label="$t('website.otherDomains')">
{{ ssl.domains }} {{ ssl.domains }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('ssl.applyType')">
{{ getProvider(ssl.provider) }}
</el-descriptions-item>
<el-descriptions-item
:label="$t('ssl.acmeAccount')"
v-if="ssl.acmeAccount && ssl.provider !== 'manual'"
>
{{ ssl.acmeAccount.email }}
</el-descriptions-item>
<el-descriptions-item :label="$t('ssl.commonName')"> <el-descriptions-item :label="$t('ssl.commonName')">
{{ ssl.type }} {{ ssl.type }}
</el-descriptions-item> </el-descriptions-item>
@ -40,6 +31,25 @@
<el-descriptions-item :label="$t('website.expireDate')"> <el-descriptions-item :label="$t('website.expireDate')">
{{ dateFormatSimple(ssl.expireDate) }} {{ dateFormatSimple(ssl.expireDate) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('ssl.applyType')">
{{ getProvider(ssl.provider) }}
</el-descriptions-item>
<el-descriptions-item
:label="$t('website.dnsAccount')"
v-if="ssl.dnsAccount && ssl.dnsAccount.id > 0"
>
{{ ssl.dnsAccount.name }}
<el-tag type="info">{{ ssl.dnsAccount.type }}</el-tag>
</el-descriptions-item>
<el-descriptions-item
:label="$t('ssl.acmeAccount')"
v-if="ssl.acmeAccount && ssl.acmeAccount.id > 0"
>
{{ ssl.acmeAccount.email }}
</el-descriptions-item>
<el-descriptions-item :label="$t('ssl.pushDir')" v-if="ssl.pushDir">
{{ ssl.dir }}
</el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
<div v-else-if="curr === 'ssl'"> <div v-else-if="curr === 'ssl'">