mirror of https://github.com/1Panel-dev/1Panel
parent
b5592327dd
commit
bb56231055
|
@ -59,4 +59,6 @@ var (
|
|||
|
||||
recycleBinService = service.NewIRecycleBinService()
|
||||
favoriteService = service.NewIFavoriteService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
)
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Page website ca
|
||||
// @Description 获取网站 ca 列表分页
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCASearch true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca/search [post]
|
||||
func (b *BaseApi) PageWebsiteCA(c *gin.Context) {
|
||||
var req request.WebsiteCASearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
total, cas, err := websiteCAService.Page(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Total: total,
|
||||
Items: cas,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Create website ca
|
||||
// @Description 创建网站 ca
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCACreate true "request"
|
||||
// @Success 200 {object} request.WebsiteCACreate
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 ca [name]","formatEN":"Create website ca [name]"}
|
||||
func (b *BaseApi) CreateWebsiteCA(c *gin.Context) {
|
||||
var req request.WebsiteCACreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteCAService.Create(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Get website ca
|
||||
// @Description 获取网站 ca
|
||||
// @Accept json
|
||||
// @Param id path int true "id"
|
||||
// @Success 200 {object} response.WebsiteCADTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca/{id} [get]
|
||||
func (b *BaseApi) GetWebsiteCA(c *gin.Context) {
|
||||
id, err := helper.GetParamID(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := websiteCAService.GetCA(id)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Delete website ca
|
||||
// @Description 删除网站 ca
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCommonReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"删除网站 ca [name]","formatEN":"Delete website ca [name]"}
|
||||
func (b *BaseApi) DeleteWebsiteCA(c *gin.Context) {
|
||||
var req request.WebsiteCommonReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteCAService.Delete(req.ID); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Obtain SSL
|
||||
// @Description 自签 SSL 证书
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCAObtain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ca/obtain [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"}
|
||||
func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) {
|
||||
var req request.WebsiteCAObtain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteCAService.ObtainSSL(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
|
@ -68,3 +68,27 @@ type WebsiteSSLUpload struct {
|
|||
CertificatePath string `json:"certificatePath"`
|
||||
Type string `json:"type" validate:"required,oneof=paste local"`
|
||||
}
|
||||
|
||||
type WebsiteCASearch struct {
|
||||
dto.PageInfo
|
||||
}
|
||||
|
||||
type WebsiteCACreate struct {
|
||||
CommonName string `json:"commonName" validate:"required"`
|
||||
Country string `json:"country" validate:"required"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Organization string `json:"organization" validate:"required"`
|
||||
OrganizationUint string `json:"organizationUint"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
|
||||
Province string `json:"province" `
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
||||
type WebsiteCAObtain struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Domains string `json:"domains" validate:"required"`
|
||||
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
|
||||
Time int `json:"time" validate:"required"`
|
||||
Unit string `json:"unit" validate:"required"`
|
||||
}
|
||||
|
|
|
@ -22,3 +22,7 @@ type WebsiteDnsAccountDTO struct {
|
|||
model.WebsiteDnsAccount
|
||||
Authorization map[string]string `json:"authorization"`
|
||||
}
|
||||
|
||||
type WebsiteCADTO struct {
|
||||
model.WebsiteCA
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package model
|
||||
|
||||
type WebsiteCA struct {
|
||||
BaseModel
|
||||
CSR string `gorm:"not null;" json:"csr"`
|
||||
Name string `gorm:"not null;" json:"name"`
|
||||
PrivateKey string `gorm:"not null" json:"privateKey"`
|
||||
KeyType string `gorm:"not null;default:2048" json:"keyType"`
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
|
||||
type WebsiteCARepo struct {
|
||||
}
|
||||
|
||||
func NewIWebsiteCARepo() IWebsiteCARepo {
|
||||
return &WebsiteCARepo{}
|
||||
}
|
||||
|
||||
type IWebsiteCARepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteCA, error)
|
||||
GetFirst(opts ...DBOption) (model.WebsiteCA, error)
|
||||
List(opts ...DBOption) ([]model.WebsiteCA, error)
|
||||
Create(ctx context.Context, ca *model.WebsiteCA) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func (w WebsiteCARepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteCA, error) {
|
||||
var caList []model.WebsiteCA
|
||||
db := getDb(opts...).Model(&model.WebsiteCA{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&caList).Error
|
||||
return count, caList, err
|
||||
}
|
||||
|
||||
func (w WebsiteCARepo) GetFirst(opts ...DBOption) (model.WebsiteCA, error) {
|
||||
var ca model.WebsiteCA
|
||||
db := getDb(opts...).Model(&model.WebsiteCA{})
|
||||
if err := db.First(&ca).Error; err != nil {
|
||||
return ca, err
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
func (w WebsiteCARepo) List(opts ...DBOption) ([]model.WebsiteCA, error) {
|
||||
var caList []model.WebsiteCA
|
||||
db := getDb(opts...).Model(&model.WebsiteCA{})
|
||||
err := db.Find(&caList).Error
|
||||
return caList, err
|
||||
}
|
||||
|
||||
func (w WebsiteCARepo) Create(ctx context.Context, ca *model.WebsiteCA) error {
|
||||
return getTx(ctx).Create(ca).Error
|
||||
}
|
||||
|
||||
func (w WebsiteCARepo) DeleteBy(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.WebsiteCA{}).Error
|
||||
}
|
|
@ -32,6 +32,7 @@ var (
|
|||
websiteDnsRepo = repo.NewIWebsiteDnsAccountRepo()
|
||||
websiteSSLRepo = repo.NewISSLRepo()
|
||||
websiteAcmeRepo = repo.NewIAcmeAccountRepo()
|
||||
websiteCARepo = repo.NewIWebsiteCARepo()
|
||||
|
||||
logRepo = repo.NewILogRepo()
|
||||
snapshotRepo = repo.NewISnapshotRepo()
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"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/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WebsiteCAService struct {
|
||||
}
|
||||
|
||||
type IWebsiteCAService interface {
|
||||
Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error)
|
||||
Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error)
|
||||
GetCA(id uint) (response.WebsiteCADTO, error)
|
||||
Delete(id uint) error
|
||||
ObtainSSL(req request.WebsiteCAObtain) error
|
||||
}
|
||||
|
||||
func NewIWebsiteCAService() IWebsiteCAService {
|
||||
return &WebsiteCAService{}
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) {
|
||||
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var caDTOs []response.WebsiteCADTO
|
||||
for _, ca := range cas {
|
||||
caDTOs = append(caDTOs, response.WebsiteCADTO{
|
||||
WebsiteCA: ca,
|
||||
})
|
||||
}
|
||||
return total, caDTOs, err
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error) {
|
||||
if exist, _ := websiteCARepo.GetFirst(commonRepo.WithByName(create.Name)); exist.ID > 0 {
|
||||
return nil, buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
|
||||
ca := &model.WebsiteCA{
|
||||
Name: create.Name,
|
||||
KeyType: create.KeyType,
|
||||
}
|
||||
|
||||
pkixName := pkix.Name{
|
||||
CommonName: create.CommonName,
|
||||
Country: []string{create.Country},
|
||||
Organization: []string{create.Organization},
|
||||
}
|
||||
if create.Province != "" {
|
||||
pkixName.Province = []string{create.Province}
|
||||
}
|
||||
if create.City != "" {
|
||||
pkixName.Locality = []string{create.City}
|
||||
}
|
||||
|
||||
rootCA := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkixName,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
}
|
||||
|
||||
privateKey, err := certcrypto.GeneratePrivateKey(ssl.KeyType(create.KeyType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
publicKey any
|
||||
caPEM = new(bytes.Buffer)
|
||||
caPrivateKeyPEM = new(bytes.Buffer)
|
||||
privateBlock = &pem.Block{}
|
||||
)
|
||||
if ssl.KeyType(create.KeyType) == certcrypto.EC256 || ssl.KeyType(create.KeyType) == certcrypto.EC384 {
|
||||
publicKey = &privateKey.(*ecdsa.PrivateKey).PublicKey
|
||||
publicKey = publicKey.(*ecdsa.PublicKey)
|
||||
privateBlock.Type = "EC PRIVATE KEY"
|
||||
privateBytes, err := x509.MarshalECPrivateKey(privateKey.(*ecdsa.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateBlock.Bytes = privateBytes
|
||||
_ = pem.Encode(caPrivateKeyPEM, privateBlock)
|
||||
} else {
|
||||
publicKey = privateKey.(*rsa.PrivateKey).PublicKey
|
||||
publicKey = publicKey.(*rsa.PublicKey)
|
||||
privateBlock.Type = "RSA PRIVATE KEY"
|
||||
privateBlock.Bytes = x509.MarshalPKCS1PrivateKey(privateKey.(*rsa.PrivateKey))
|
||||
}
|
||||
ca.PrivateKey = string(pem.EncodeToMemory(privateBlock))
|
||||
|
||||
caBytes, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, publicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: caBytes,
|
||||
}
|
||||
_ = pem.Encode(caPEM, certBlock)
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
ca.CSR = string(pemData)
|
||||
|
||||
if err := websiteCARepo.Create(context.Background(), ca); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &create, nil
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) GetCA(id uint) (response.WebsiteCADTO, error) {
|
||||
ca, err := websiteCARepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return response.WebsiteCADTO{}, err
|
||||
}
|
||||
return response.WebsiteCADTO{
|
||||
WebsiteCA: ca,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Delete(id uint) error {
|
||||
return websiteCARepo.DeleteBy(commonRepo.WithByID(id))
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
|
||||
ca, err := websiteCARepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newSSL := &model.WebsiteSSL{
|
||||
Provider: constant.SelfSigned,
|
||||
KeyType: req.KeyType,
|
||||
}
|
||||
|
||||
var (
|
||||
domains []string
|
||||
ips []net.IP
|
||||
)
|
||||
if req.Domains != "" {
|
||||
domainArray := strings.Split(req.Domains, "\n")
|
||||
for _, domain := range domainArray {
|
||||
if !common.IsValidDomain(domain) {
|
||||
err = buserr.WithName("ErrDomainFormat", domain)
|
||||
return err
|
||||
} else {
|
||||
if ipAddress := net.ParseIP(domain); ipAddress == nil {
|
||||
domains = append(domains, domain)
|
||||
} else {
|
||||
ips = append(ips, ipAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
newSSL.PrimaryDomain = domains[0]
|
||||
newSSL.Domains = strings.Join(domains[1:], ",")
|
||||
}
|
||||
}
|
||||
|
||||
rootCertBlock, _ := pem.Decode([]byte(ca.CSR))
|
||||
if rootCertBlock == nil {
|
||||
return buserr.New("ErrSSLCertificateFormat")
|
||||
}
|
||||
rootCsr, err := x509.ParseCertificate(rootCertBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootPrivateKeyBlock, _ := pem.Decode([]byte(ca.PrivateKey))
|
||||
if rootPrivateKeyBlock == nil {
|
||||
return buserr.New("ErrSSLCertificateFormat")
|
||||
}
|
||||
|
||||
var rootPrivateKey any
|
||||
if ssl.KeyType(ca.KeyType) == certcrypto.EC256 || ssl.KeyType(ca.KeyType) == certcrypto.EC384 {
|
||||
rootPrivateKey, err = x509.ParseECPrivateKey(rootPrivateKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
rootPrivateKey, err = x509.ParsePKCS1PrivateKey(rootPrivateKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
interPrivateKey, interPublicKey, _, err := createPrivateKey(req.KeyType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notAfter := time.Now()
|
||||
if req.Unit == "year" {
|
||||
notAfter = notAfter.AddDate(req.Time, 0, 0)
|
||||
} else {
|
||||
notAfter = notAfter.AddDate(0, 0, req.Time)
|
||||
}
|
||||
interCsr := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: rootCsr.Subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
}
|
||||
interDer, err := x509.CreateCertificate(rand.Reader, interCsr, rootCsr, interPublicKey, rootPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
interCert, err := x509.ParseCertificate(interDer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, publicKey, privateKeyBytes, err := createPrivateKey(req.KeyType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
csr := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: rootCsr.Subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: domains,
|
||||
IPAddresses: ips,
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, csr, interCert, publicKey, interPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
newSSL.Pem = string(pemData)
|
||||
newSSL.PrivateKey = string(privateKeyBytes)
|
||||
newSSL.ExpireDate = cert.NotAfter
|
||||
newSSL.StartDate = cert.NotBefore
|
||||
newSSL.Type = cert.Issuer.CommonName
|
||||
newSSL.Organization = rootCsr.Subject.Organization[0]
|
||||
|
||||
return websiteSSLRepo.Create(context.Background(), newSSL)
|
||||
}
|
||||
|
||||
func createPrivateKey(keyType string) (privateKey any, publicKey any, privateKeyBytes []byte, err error) {
|
||||
privateKey, err = certcrypto.GeneratePrivateKey(ssl.KeyType(keyType))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
caPrivateKeyPEM = new(bytes.Buffer)
|
||||
)
|
||||
if ssl.KeyType(keyType) == certcrypto.EC256 || ssl.KeyType(keyType) == certcrypto.EC384 {
|
||||
publicKey = &privateKey.(*ecdsa.PrivateKey).PublicKey
|
||||
publicKey = publicKey.(*ecdsa.PublicKey)
|
||||
block := &pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
}
|
||||
privateBytes, sErr := x509.MarshalECPrivateKey(privateKey.(*ecdsa.PrivateKey))
|
||||
if sErr != nil {
|
||||
err = sErr
|
||||
return
|
||||
}
|
||||
block.Bytes = privateBytes
|
||||
_ = pem.Encode(caPrivateKeyPEM, block)
|
||||
} else {
|
||||
publicKey = privateKey.(*rsa.PrivateKey).PublicKey
|
||||
publicKey = publicKey.(*rsa.PublicKey)
|
||||
_ = pem.Encode(caPrivateKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey.(*rsa.PrivateKey)),
|
||||
})
|
||||
}
|
||||
privateKeyBytes = caPrivateKeyPEM.Bytes()
|
||||
return
|
||||
}
|
|
@ -148,7 +148,6 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
|
|||
}
|
||||
|
||||
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
||||
|
||||
var (
|
||||
err error
|
||||
websiteSSL model.WebsiteSSL
|
||||
|
@ -212,7 +211,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
|||
}
|
||||
|
||||
go func() {
|
||||
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
defer logFile.Close()
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
legoLogger.Logger = logger
|
||||
|
@ -443,10 +442,17 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
|||
if len(cert.DNSNames) > 0 {
|
||||
newSSL.PrimaryDomain = cert.DNSNames[0]
|
||||
domains = cert.DNSNames[1:]
|
||||
} else if len(cert.IPAddresses) > 0 {
|
||||
newSSL.PrimaryDomain = cert.IPAddresses[0].String()
|
||||
for _, ip := range cert.IPAddresses[1:] {
|
||||
domains = append(domains, ip.String())
|
||||
}
|
||||
if len(cert.IPAddresses) > 0 {
|
||||
if newSSL.PrimaryDomain == "" {
|
||||
newSSL.PrimaryDomain = cert.IPAddresses[0].String()
|
||||
for _, ip := range cert.IPAddresses[1:] {
|
||||
domains = append(domains, ip.String())
|
||||
}
|
||||
} else {
|
||||
for _, ip := range cert.IPAddresses {
|
||||
domains = append(domains, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
newSSL.Domains = strings.Join(domains, ",")
|
||||
|
|
|
@ -27,6 +27,7 @@ const (
|
|||
DnsManual = "dnsManual"
|
||||
Http = "http"
|
||||
Manual = "manual"
|
||||
SelfSigned = "selfSigned"
|
||||
|
||||
StartWeb = "start"
|
||||
StopWeb = "stop"
|
||||
|
|
|
@ -55,6 +55,7 @@ func Init() {
|
|||
|
||||
migrations.UpdateAcmeAccount,
|
||||
migrations.UpdateWebsiteSSL,
|
||||
migrations.AddWebsiteCA,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
@ -25,3 +25,13 @@ var UpdateWebsiteSSL = &gormigrate.Migration{
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddWebsiteCA = &gormigrate.Migration{
|
||||
ID: "20231125-add-website-ca",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.WebsiteCA{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ func Routers() *gin.Engine {
|
|||
systemRouter.InitRuntimeRouter(PrivateGroup)
|
||||
systemRouter.InitProcessRouter(PrivateGroup)
|
||||
systemRouter.InitToolboxRouter(PrivateGroup)
|
||||
systemRouter.InitWebsiteCARouter(PrivateGroup)
|
||||
}
|
||||
|
||||
return Router
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (a *WebsiteDnsAccountRouter) InitWebsiteCARouter(Router *gin.RouterGroup) {
|
||||
groupRouter := Router.Group("websites/ca")
|
||||
groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
groupRouter.POST("/search", baseApi.PageWebsiteCA)
|
||||
groupRouter.POST("", baseApi.CreateWebsiteCA)
|
||||
groupRouter.POST("/del", baseApi.DeleteWebsiteCA)
|
||||
groupRouter.POST("/obtain", baseApi.ObtainWebsiteCA)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
@ -11016,6 +11016,223 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Create website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCACreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCACreate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "Create website ca [name]",
|
||||
"formatZH": "创建网站 ca [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Delete website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCommonReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Delete website ca [name]",
|
||||
"formatZH": "删除网站 ca [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "自签 SSL 证书",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Obtain SSL",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCAObtain"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Obtain SSL [name]",
|
||||
"formatZH": "自签 SSL 证书 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取网站 ca 列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Page website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCASearch"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Get website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.WebsiteCADTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -18667,6 +18884,104 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCACreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"commonName",
|
||||
"country",
|
||||
"email",
|
||||
"keyType",
|
||||
"name",
|
||||
"organization"
|
||||
],
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string"
|
||||
},
|
||||
"commonName": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"P256",
|
||||
"P384",
|
||||
"2048",
|
||||
"3072",
|
||||
"4096",
|
||||
"8192"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"organizationUint": {
|
||||
"type": "string"
|
||||
},
|
||||
"province": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCAObtain": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"domains",
|
||||
"id",
|
||||
"keyType",
|
||||
"time",
|
||||
"unit"
|
||||
],
|
||||
"properties": {
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"P256",
|
||||
"P384",
|
||||
"2048",
|
||||
"3072",
|
||||
"4096",
|
||||
"8192"
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCASearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCommonReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -19793,6 +20108,32 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteCADTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"csr": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteDNSRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -11009,6 +11009,223 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Create website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCACreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCACreate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [],
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"formatEN": "Create website ca [name]",
|
||||
"formatZH": "创建网站 ca [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "删除网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Delete website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCommonReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Delete website ca [name]",
|
||||
"formatZH": "删除网站 ca [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "自签 SSL 证书",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Obtain SSL",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCAObtain"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_cas",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "name",
|
||||
"output_value": "name"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Obtain SSL [name]",
|
||||
"formatZH": "自签 SSL 证书 [name]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/search": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取网站 ca 列表分页",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Page website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteCASearch"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ca/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取网站 ca",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website CA"
|
||||
],
|
||||
"summary": "Get website ca",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.WebsiteCADTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -18660,6 +18877,104 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCACreate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"commonName",
|
||||
"country",
|
||||
"email",
|
||||
"keyType",
|
||||
"name",
|
||||
"organization"
|
||||
],
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string"
|
||||
},
|
||||
"commonName": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"P256",
|
||||
"P384",
|
||||
"2048",
|
||||
"3072",
|
||||
"4096",
|
||||
"8192"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"organizationUint": {
|
||||
"type": "string"
|
||||
},
|
||||
"province": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCAObtain": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"domains",
|
||||
"id",
|
||||
"keyType",
|
||||
"time",
|
||||
"unit"
|
||||
],
|
||||
"properties": {
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"P256",
|
||||
"P384",
|
||||
"2048",
|
||||
"3072",
|
||||
"4096",
|
||||
"8192"
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCASearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"page",
|
||||
"pageSize"
|
||||
],
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.WebsiteCommonReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -19786,6 +20101,32 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteCADTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"csr": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"keyType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.WebsiteDNSRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -3710,6 +3710,77 @@ definitions:
|
|||
- keyType
|
||||
- type
|
||||
type: object
|
||||
request.WebsiteCACreate:
|
||||
properties:
|
||||
city:
|
||||
type: string
|
||||
commonName:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
keyType:
|
||||
enum:
|
||||
- P256
|
||||
- P384
|
||||
- "2048"
|
||||
- "3072"
|
||||
- "4096"
|
||||
- "8192"
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
organizationUint:
|
||||
type: string
|
||||
province:
|
||||
type: string
|
||||
required:
|
||||
- commonName
|
||||
- country
|
||||
- email
|
||||
- keyType
|
||||
- name
|
||||
- organization
|
||||
type: object
|
||||
request.WebsiteCAObtain:
|
||||
properties:
|
||||
domains:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
keyType:
|
||||
enum:
|
||||
- P256
|
||||
- P384
|
||||
- "2048"
|
||||
- "3072"
|
||||
- "4096"
|
||||
- "8192"
|
||||
type: string
|
||||
time:
|
||||
type: integer
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- domains
|
||||
- id
|
||||
- keyType
|
||||
- time
|
||||
- unit
|
||||
type: object
|
||||
request.WebsiteCASearch:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
pageSize:
|
||||
type: integer
|
||||
required:
|
||||
- page
|
||||
- pageSize
|
||||
type: object
|
||||
request.WebsiteCommonReq:
|
||||
properties:
|
||||
id:
|
||||
|
@ -4461,6 +4532,23 @@ definitions:
|
|||
url:
|
||||
type: string
|
||||
type: object
|
||||
response.WebsiteCADTO:
|
||||
properties:
|
||||
createdAt:
|
||||
type: string
|
||||
csr:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
keyType:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
privateKey:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
response.WebsiteDNSRes:
|
||||
properties:
|
||||
domain:
|
||||
|
@ -11567,6 +11655,144 @@ paths:
|
|||
summary: Get AuthBasic conf
|
||||
tags:
|
||||
- Website
|
||||
/websites/ca:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 创建网站 ca
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCACreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCACreate'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Create website ca
|
||||
tags:
|
||||
- Website CA
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- name
|
||||
formatEN: Create website ca [name]
|
||||
formatZH: 创建网站 ca [name]
|
||||
paramKeys: []
|
||||
/websites/ca/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取网站 ca
|
||||
parameters:
|
||||
- description: id
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.WebsiteCADTO'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Get website ca
|
||||
tags:
|
||||
- Website CA
|
||||
/websites/ca/del:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 删除网站 ca
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCommonReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Delete website ca
|
||||
tags:
|
||||
- Website CA
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: website_cas
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: name
|
||||
output_value: name
|
||||
bodyKeys:
|
||||
- id
|
||||
formatEN: Delete website ca [name]
|
||||
formatZH: 删除网站 ca [name]
|
||||
paramKeys: []
|
||||
/websites/ca/obtain:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 自签 SSL 证书
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCAObtain'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Obtain SSL
|
||||
tags:
|
||||
- Website CA
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: website_cas
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: name
|
||||
output_value: name
|
||||
bodyKeys:
|
||||
- id
|
||||
formatEN: Obtain SSL [name]
|
||||
formatZH: 自签 SSL 证书 [name]
|
||||
paramKeys: []
|
||||
/websites/ca/search:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取网站 ca 列表分页
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteCASearch'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageResult'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Page website ca
|
||||
tags:
|
||||
- Website CA
|
||||
/websites/check:
|
||||
post:
|
||||
consumes:
|
||||
|
|
|
@ -456,4 +456,31 @@ export namespace Website {
|
|||
export interface SSLObtain {
|
||||
ID: number;
|
||||
}
|
||||
|
||||
export interface CA extends CommonModel {
|
||||
name: string;
|
||||
csr: string;
|
||||
privateKey: string;
|
||||
keyType: string;
|
||||
}
|
||||
|
||||
export interface CACreate {
|
||||
name: string;
|
||||
commonName: string;
|
||||
country: string;
|
||||
email: string;
|
||||
organization: string;
|
||||
organizationUint: string;
|
||||
keyType: string;
|
||||
province: string;
|
||||
city: string;
|
||||
}
|
||||
|
||||
export interface SSLObtainByCA {
|
||||
id: number;
|
||||
domains: string;
|
||||
keyType: string;
|
||||
time: number;
|
||||
unit: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,3 +247,19 @@ export const GetDirConfig = (req: Website.ProxyReq) => {
|
|||
export const UploadSSL = (req: Website.SSLUpload) => {
|
||||
return http.post<any>(`/websites/ssl/upload`, req);
|
||||
};
|
||||
|
||||
export const SearchCAs = (req: ReqPage) => {
|
||||
return http.post<ResPage<Website.CA>>(`/websites/ca/search`, req);
|
||||
};
|
||||
|
||||
export const CreateCA = (req: Website.CACreate) => {
|
||||
return http.post<Website.CA>(`/websites/ca`, req);
|
||||
};
|
||||
|
||||
export const ObtainSSLByCA = (req: Website.SSLObtainByCA) => {
|
||||
return http.post<any>(`/websites/ca/obtain`, req);
|
||||
};
|
||||
|
||||
export const DeleteCA = (req: Website.DelReq) => {
|
||||
return http.post<any>(`/websites/ca/del`, req);
|
||||
};
|
||||
|
|
|
@ -132,7 +132,7 @@ const checkName = (rule: any, value: any, callback: any) => {
|
|||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.commonName')));
|
||||
} else {
|
||||
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,29}$/;
|
||||
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,128}$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.commonName')));
|
||||
} else {
|
||||
|
|
|
@ -137,3 +137,34 @@ export const KeyTypes = [
|
|||
{ label: 'RSA 3072', value: '3072' },
|
||||
{ label: 'RSA 4096', value: '4096' },
|
||||
];
|
||||
|
||||
export const DNSTypes = [
|
||||
{
|
||||
label: i18n.global.t('website.aliyun'),
|
||||
value: 'AliYun',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod',
|
||||
value: 'DnsPod',
|
||||
},
|
||||
{
|
||||
label: 'CloudFlare',
|
||||
value: 'CloudFlare',
|
||||
},
|
||||
{
|
||||
label: 'NameSilo',
|
||||
value: 'NameSilo',
|
||||
},
|
||||
{
|
||||
label: 'NameCheap',
|
||||
value: 'NameCheap',
|
||||
},
|
||||
{
|
||||
label: 'Name.com',
|
||||
value: 'NameCom',
|
||||
},
|
||||
{
|
||||
label: 'Godaddy',
|
||||
value: 'Godaddy',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -156,7 +156,7 @@ const message = {
|
|||
requiredInput: 'Please enter the required fields',
|
||||
requiredSelect: 'Please select the required fields',
|
||||
illegalInput: 'There are illegal characters in the input box.',
|
||||
commonName: 'Support English, Chinese, numbers, .-, and _ length 1-30',
|
||||
commonName: 'Support English, Chinese, numbers, .-, and _ length 1-128',
|
||||
userName: 'Support English, Chinese, numbers and _ length 3-30',
|
||||
simpleName: 'Support English, numbers and _ length 1-30',
|
||||
dbName: 'Support English, Chinese, numbers, .-, and _ length 1-64',
|
||||
|
@ -1557,7 +1557,7 @@ const message = {
|
|||
provider: 'Verification method',
|
||||
dnsManual: 'Manual resolution',
|
||||
expireDate: 'Expiration Time',
|
||||
brand: 'Issuer',
|
||||
brand: 'Organization',
|
||||
deploySSL: 'Deployment',
|
||||
deploySSLHelper: 'Are you sure to deploy the certificate? ',
|
||||
ssl: 'Certificate',
|
||||
|
@ -1822,6 +1822,20 @@ const message = {
|
|||
apply: 'Apply',
|
||||
applyStart: 'Certificate application starts',
|
||||
getDnsResolve: 'Getting DNS resolution value, please wait...',
|
||||
selfSigned: 'Self-signed certificate',
|
||||
ca: 'Certification Authority',
|
||||
createCA: 'Create institution',
|
||||
commonName: 'Certificate subject name (CN)',
|
||||
caName: 'Institution name',
|
||||
company: 'company/organization',
|
||||
department: 'department',
|
||||
city: 'city',
|
||||
province: 'province',
|
||||
country: 'country code',
|
||||
commonNameHelper: 'For example:',
|
||||
selfSign: 'Issue certificate',
|
||||
days: 'validity period',
|
||||
domainHelper: 'One domain name per line, supports * and IP address',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Create rule',
|
||||
|
|
|
@ -157,7 +157,7 @@ const message = {
|
|||
requiredInput: '請填寫必填項',
|
||||
requiredSelect: '請選擇必選項',
|
||||
illegalInput: '輸入框中存在不合法字符',
|
||||
commonName: '支持英文、中文、數字、.-和_,長度1-30',
|
||||
commonName: '支持英文、中文、數字、.-和_,長度1-128',
|
||||
userName: '支持英文、中文、數字和_,長度3-30',
|
||||
simpleName: '支持英文、數字、_,長度1-30',
|
||||
dbName: '支持英文、中文、數字、.-_,長度1-64',
|
||||
|
@ -1458,7 +1458,7 @@ const message = {
|
|||
provider: '驗證方式',
|
||||
dnsManual: '手動解析',
|
||||
expireDate: '過期時間',
|
||||
brand: '頒發者',
|
||||
brand: '組織',
|
||||
deploySSL: '部署',
|
||||
deploySSLHelper: '確定部署證書?',
|
||||
ssl: '證書',
|
||||
|
@ -1710,6 +1710,20 @@ const message = {
|
|||
apply: '申請',
|
||||
applyStart: '證書申請開始',
|
||||
getDnsResolve: '正在取得 DNS 解析值,請稍後 ...',
|
||||
selfSigned: '自簽證書',
|
||||
ca: '證書頒發機構',
|
||||
createCA: '創建機構',
|
||||
commonName: '憑證主體名稱(CN)',
|
||||
caName: '機構名稱',
|
||||
company: '公司/組織',
|
||||
department: '部門',
|
||||
city: '城市',
|
||||
province: '省份',
|
||||
country: '國家代號',
|
||||
commonNameHelper: '例如:',
|
||||
selfSign: '簽發證書',
|
||||
days: '有效期限',
|
||||
domainHelper: '一行一個網域名稱,支援*和IP位址',
|
||||
},
|
||||
firewall: {
|
||||
create: '創建規則',
|
||||
|
|
|
@ -157,7 +157,7 @@ const message = {
|
|||
requiredInput: '请填写必填项',
|
||||
requiredSelect: '请选择必选项',
|
||||
illegalInput: '输入框中存在不合法字符',
|
||||
commonName: '支持英文、中文、数字、.-和_,长度1-30',
|
||||
commonName: '支持英文、中文、数字、.-和_,长度1-128',
|
||||
userName: '支持英文、中文、数字和_,长度3-30',
|
||||
simpleName: '支持英文、数字、_,长度1-30',
|
||||
dbName: '支持英文、中文、数字、.-_,长度1-64',
|
||||
|
@ -1458,7 +1458,7 @@ const message = {
|
|||
provider: '验证方式',
|
||||
dnsManual: '手动解析',
|
||||
expireDate: '过期时间',
|
||||
brand: '颁发者',
|
||||
brand: '颁发组织',
|
||||
deploySSL: '部署',
|
||||
deploySSLHelper: '确定部署证书?',
|
||||
ssl: '证书',
|
||||
|
@ -1710,6 +1710,20 @@ const message = {
|
|||
apply: '申请',
|
||||
applyStart: '证书申请开始',
|
||||
getDnsResolve: '正在获取 DNS 解析值,请稍后 ...',
|
||||
selfSigned: '自签证书',
|
||||
ca: '证书颁发机构',
|
||||
createCA: '创建机构',
|
||||
commonName: '证书主体名称(CN)',
|
||||
caName: '机构名称',
|
||||
company: '公司/组织',
|
||||
department: '部门',
|
||||
city: '城市',
|
||||
province: '省份',
|
||||
country: '国家代号',
|
||||
commonNameHelper: '例如:',
|
||||
selfSign: '签发证书',
|
||||
days: '有效期',
|
||||
domainHelper: '一行一个域名,支持*和IP地址',
|
||||
},
|
||||
firewall: {
|
||||
create: '创建规则',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AcmeAccountTypes, DNSTypes, KeyTypes } from '@/global/mimetype';
|
||||
import i18n from '@/lang';
|
||||
|
||||
export function deepCopy<T>(obj: any): T {
|
||||
|
@ -322,6 +323,8 @@ export function getProvider(provider: string): string {
|
|||
return i18n.global.t('website.dnsManual');
|
||||
case 'http':
|
||||
return 'HTTP';
|
||||
case 'selfSigned':
|
||||
return i18n.global.t('ssl.selfSigned');
|
||||
default:
|
||||
return i18n.global.t('ssl.manualCreate');
|
||||
}
|
||||
|
@ -437,3 +440,30 @@ export function getDateStr() {
|
|||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
export function getAccountName(type: string) {
|
||||
for (const i of AcmeAccountTypes) {
|
||||
if (i.value === type) {
|
||||
return i.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getKeyName(type: string) {
|
||||
for (const i of KeyTypes) {
|
||||
if (i.value === type) {
|
||||
return i.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getDNSName(type: string) {
|
||||
for (const i of DNSTypes) {
|
||||
if (i.value === type) {
|
||||
return i.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
></el-table-column>
|
||||
<el-table-column :label="$t('website.acmeAccountType')" fix show-overflow-tooltip prop="type">
|
||||
<template #default="{ row }">
|
||||
{{ getAccountType(row.type) }}
|
||||
{{ getAccountName(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" fix show-overflow-tooltip prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyType(row.keyType) }}
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="URL" show-overflow-tooltip prop="url" min-width="300px"></el-table-column>
|
||||
|
@ -38,7 +38,6 @@
|
|||
</ComplexTable>
|
||||
<Create ref="createRef" @close="search()"></Create>
|
||||
</el-drawer>
|
||||
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
</template>
|
||||
|
||||
|
@ -50,7 +49,7 @@ import { DeleteAcmeAccount, SearchAcmeAccount } from '@/api/modules/website';
|
|||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import Create from './create/index.vue';
|
||||
import { AcmeAccountTypes, KeyTypes } from '@/global/mimetype';
|
||||
import { getAccountName, getKeyName } from '@/utils/util';
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
|
@ -110,21 +109,6 @@ const deleteAccount = async (row: any) => {
|
|||
});
|
||||
};
|
||||
|
||||
const getAccountType = (type: string) => {
|
||||
for (const i of AcmeAccountTypes) {
|
||||
if (i.value === type) {
|
||||
return i.label;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getKeyType = (type: string) => {
|
||||
for (const i of KeyTypes) {
|
||||
if (i.value === type) {
|
||||
return i.label;
|
||||
}
|
||||
}
|
||||
};
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('ssl.createCA')"
|
||||
:close-on-click-modal="false"
|
||||
width="40%"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form @submit.prevent ref="caForm" label-position="top" :model="ca" :rules="rules">
|
||||
<el-form-item :label="$t('ssl.caName')" prop="name">
|
||||
<el-input v-model.trim="ca.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.commonName')" prop="commonName">
|
||||
<el-input v-model.trim="ca.commonName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.email')" prop="email">
|
||||
<el-input v-model.trim="ca.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.company')" prop="organization">
|
||||
<el-input v-model.trim="ca.organization"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.department')" prop="organizationUint">
|
||||
<el-input v-model.trim="ca.organizationUint"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.country')" prop="country">
|
||||
<el-input v-model.trim="ca.country"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.province')" prop="province">
|
||||
<el-input v-model.trim="ca.province"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.city')" prop="city">
|
||||
<el-input v-model.trim="ca.city"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.keyType')" prop="keyType">
|
||||
<el-select v-model="ca.keyType">
|
||||
<el-option
|
||||
v-for="(keyType, index) in KeyTypes"
|
||||
:key="index"
|
||||
:label="keyType.label"
|
||||
:value="keyType.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(caForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
import { CreateCA } from '@/api/modules/website';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const caForm = ref<FormInstance>();
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const rules = ref({
|
||||
email: [Rules.requiredInput, Rules.email],
|
||||
keyType: [Rules.requiredSelect],
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
country: [Rules.requiredSelect],
|
||||
organization: [Rules.requiredInput],
|
||||
commonName: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const initData = () => ({
|
||||
name: '',
|
||||
email: '',
|
||||
keyType: 'P256',
|
||||
commonName: '',
|
||||
country: 'CN',
|
||||
organization: '',
|
||||
organizationUint: '',
|
||||
province: '',
|
||||
city: '',
|
||||
});
|
||||
|
||||
const ca = ref(initData());
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
caForm.value.resetFields();
|
||||
ca.value = initData();
|
||||
};
|
||||
|
||||
const acceptParams = () => {
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
CreateCA(ca.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ssl.ca')" :back="handleClose" />
|
||||
</template>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openCreate">{{ $t('ssl.createCA') }}</el-button>
|
||||
</template>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
prop="name"
|
||||
min-width="100px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" fix show-overflow-tooltip prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="1"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
<Create ref="createRef" @close="search()" />
|
||||
<Obtain ref="obtainRef" @close="handleClose()" />
|
||||
</el-drawer>
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import OpDialog from '@/components/del-dialog/index.vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { DeleteCA, SearchCAs } from '@/api/modules/website';
|
||||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import Create from './create/index.vue';
|
||||
import { getKeyName } from '@/utils/util';
|
||||
import Obtain from './obtain/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const data = ref();
|
||||
const createRef = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'ca-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
const opRef = ref();
|
||||
const obtainRef = ref();
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('ssl.selfSign'),
|
||||
click: function (row: Website.CA) {
|
||||
obtain(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Website.CA) {
|
||||
deleteCA(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const obtain = (row: any) => {
|
||||
obtainRef.value.acceptParams(row.id);
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const req = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
await SearchCAs(req).then((res) => {
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
});
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
createRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
em('close', false);
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const deleteCA = async (row: any) => {
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: [row.name],
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('website.ca'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: DeleteCA,
|
||||
params: { id: row.id },
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('ssl.selfSigned')"
|
||||
:close-on-click-modal="false"
|
||||
width="40%"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form @submit.prevent ref="obtainForm" label-position="top" :model="obtain" :rules="rules">
|
||||
<el-form-item :label="$t('website.domain')" prop="domains">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 4, maxRows: 10 }"
|
||||
v-model="obtain.domains"
|
||||
:placeholder="$t('website.domainHelper')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.keyType')" prop="keyType">
|
||||
<el-select v-model="obtain.keyType">
|
||||
<el-option
|
||||
v-for="(keyType, index) in KeyTypes"
|
||||
:key="index"
|
||||
:label="keyType.label"
|
||||
:value="keyType.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.days')" prop="time">
|
||||
<el-input type="number" v-model.number="obtain.time">
|
||||
<template #append>
|
||||
<el-select v-model="obtain.unit" style="width: 100px">
|
||||
<el-option :label="$t('commons.units.day')" value="day"></el-option>
|
||||
<el-option :label="$t('commons.units.year')" value="year"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(obtainForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ObtainSSLByCA } from '@/api/modules/website';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const obtainForm = ref<FormInstance>();
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const rules = ref({
|
||||
keyType: [Rules.requiredSelect],
|
||||
domains: [Rules.requiredInput],
|
||||
time: [Rules.requiredInput, checkNumberRange(1, 1000)],
|
||||
});
|
||||
|
||||
const initData = () => ({
|
||||
keyType: 'P256',
|
||||
domains: '',
|
||||
id: 0,
|
||||
time: 0,
|
||||
unit: 'day',
|
||||
});
|
||||
const obtain = ref(initData());
|
||||
|
||||
const acceptParams = (id: number) => {
|
||||
open.value = true;
|
||||
obtain.value.id = id;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
obtainForm.value.resetFields();
|
||||
obtain.value = initData();
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
ObtainSSLByCA(obtain.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
|
@ -52,9 +52,20 @@
|
|||
<el-option
|
||||
v-for="(dns, index) in dnsAccounts"
|
||||
:key="index"
|
||||
:label="dns.name + ' ( ' + dns.type + ' )'"
|
||||
:label="dns.name"
|
||||
:value="dns.id"
|
||||
></el-option>
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<span>{{ dns.name }}</span>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<span>
|
||||
<el-tag type="success">{{ dns.type }}</el-tag>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="autoRenew" v-if="ssl.provider !== 'dnsManual'">
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
>
|
||||
{{ ssl.acmeAccount.email }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('commons.table.type')">
|
||||
<el-descriptions-item :label="$t('ssl.commonName')">
|
||||
{{ ssl.type }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.brand')">
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<el-form-item :label="$t('commons.table.type')" prop="type">
|
||||
<el-select v-model="account.type" :disabled="accountData.mode === 'edit'">
|
||||
<el-option
|
||||
v-for="(type, index) in types"
|
||||
v-for="(type, index) in DNSTypes"
|
||||
:key="index"
|
||||
:label="type.label"
|
||||
:value="type.value"
|
||||
|
@ -92,6 +92,7 @@ import i18n from '@/lang';
|
|||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { DNSTypes } from '@/global/mimetype';
|
||||
|
||||
interface AccountProps {
|
||||
mode: string;
|
||||
|
@ -102,37 +103,6 @@ const accountData = ref<AccountProps>({
|
|||
form: {},
|
||||
});
|
||||
|
||||
const types = [
|
||||
{
|
||||
label: i18n.global.t('website.aliyun'),
|
||||
value: 'AliYun',
|
||||
},
|
||||
{
|
||||
label: 'DNSPod',
|
||||
value: 'DnsPod',
|
||||
},
|
||||
{
|
||||
label: 'CloudFlare',
|
||||
value: 'CloudFlare',
|
||||
},
|
||||
{
|
||||
label: 'NameSilo',
|
||||
value: 'NameSilo',
|
||||
},
|
||||
{
|
||||
label: 'NameCheap',
|
||||
value: 'NameCheap',
|
||||
},
|
||||
{
|
||||
label: 'Name.com',
|
||||
value: 'NameCom',
|
||||
},
|
||||
{
|
||||
label: 'Godaddy',
|
||||
value: 'Godaddy',
|
||||
},
|
||||
];
|
||||
|
||||
const open = ref();
|
||||
const loading = ref(false);
|
||||
const accountForm = ref<FormInstance>();
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
<el-table-column :label="$t('commons.table.name')" fix show-overflow-tooltip prop="name"></el-table-column>
|
||||
<el-table-column :label="$t('commons.table.type')" prop="type">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.type == 'AliYun'">{{ $t('website.aliyun') }}</span>
|
||||
<span v-else>{{ row.type }}</span>
|
||||
<span>{{ getDNSName(row.type) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
|
@ -37,6 +36,7 @@ import { Website } from '@/api/interface/website';
|
|||
import { DeleteDnsAccount, SearchDnsAccount } from '@/api/modules/website';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { getDNSName } from '@/utils/util';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'dns-account-page-size',
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
<el-button type="primary" @click="openUpload()">
|
||||
{{ $t('ssl.upload') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="openCA()">
|
||||
{{ $t('ssl.selfSigned') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="openAcmeAccount()">
|
||||
{{ $t('website.acmeAccountManage') }}
|
||||
</el-button>
|
||||
|
@ -95,7 +98,11 @@
|
|||
<el-table-column :label="$t('ssl.autoRenew')" fix width="100px">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
:disabled="row.provider === 'dnsManual' || row.provider === 'manual'"
|
||||
:disabled="
|
||||
row.provider === 'dnsManual' ||
|
||||
row.provider === 'manual' ||
|
||||
row.provider === 'selfSigned'
|
||||
"
|
||||
v-model="row.autoRenew"
|
||||
@change="updateConfig(row)"
|
||||
/>
|
||||
|
@ -124,6 +131,7 @@
|
|||
<Apply ref="applyRef" @search="search" />
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
<Log ref="logRef" @close="search()" />
|
||||
<CA ref="caRef" @close="search()" />
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -134,6 +142,7 @@ import OpDialog from '@/components/del-dialog/index.vue';
|
|||
import { DeleteSSL, ObtainSSL, SearchSSL, UpdateSSL } from '@/api/modules/website';
|
||||
import DnsAccount from './dns-account/index.vue';
|
||||
import AcmeAccount from './acme-account/index.vue';
|
||||
import CA from './ca/index.vue';
|
||||
import Create from './create/index.vue';
|
||||
import Detail from './detail/index.vue';
|
||||
import { dateFormat, getProvider } from '@/utils/util';
|
||||
|
@ -162,6 +171,7 @@ const opRef = ref();
|
|||
const sslUploadRef = ref();
|
||||
const applyRef = ref();
|
||||
const logRef = ref();
|
||||
const caRef = ref();
|
||||
|
||||
const routerButton = [
|
||||
{
|
||||
|
@ -183,7 +193,7 @@ const buttons = [
|
|||
{
|
||||
label: i18n.global.t('ssl.apply'),
|
||||
disabled: function (row: Website.SSLDTO) {
|
||||
return row.status === 'applying';
|
||||
return row.status === 'applying' || row.provider === 'manual' || row.provider === 'selfSigned';
|
||||
},
|
||||
click: function (row: Website.SSLDTO) {
|
||||
if (row.provider === 'dnsManual') {
|
||||
|
@ -250,6 +260,9 @@ const openDetail = (id: number) => {
|
|||
const openLog = (row: Website.SSLDTO) => {
|
||||
logRef.value.acceptParams({ id: row.id, type: 'ssl' });
|
||||
};
|
||||
const openCA = () => {
|
||||
caRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const applySSL = (row: Website.SSLDTO) => {
|
||||
loading.value = true;
|
||||
|
|
Loading…
Reference in New Issue