mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-26 00:01:11 +00:00
feat: ca website upload (#14148)
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -176,3 +179,64 @@ func (b *BaseApi) DownloadCAFile(c *gin.Context) {
|
||||
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
|
||||
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Upload website CA archive
|
||||
// @Accept multipart/form-data
|
||||
// @Param file formData file true "ZIP containing ca.crt and ca.key"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ca/upload [post]
|
||||
func (b *BaseApi) UploadWebsiteCA(c *gin.Context) {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 11<<20)
|
||||
if err := c.Request.ParseMultipartForm(11 << 20); err != nil {
|
||||
helper.InternalServer(c, buserr.New("ErrCAUpload"))
|
||||
return
|
||||
}
|
||||
if c.Request.MultipartForm != nil {
|
||||
defer c.Request.MultipartForm.RemoveAll()
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil || file.Size > 10<<20 || !strings.HasSuffix(strings.ToLower(file.Filename), ".zip") {
|
||||
helper.InternalServer(c, buserr.New("ErrCAUpload"))
|
||||
return
|
||||
}
|
||||
stream, err := file.Open()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
data, err := io.ReadAll(io.LimitReader(stream, (10<<20)+1))
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
if err = websiteCAService.Upload(file.Filename[:len(file.Filename)-len(".zip")], data); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website CA
|
||||
// @Summary Import website CA from PEM or server files
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteCAImport true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ca/import [post]
|
||||
func (b *BaseApi) ImportWebsiteCA(c *gin.Context) {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 3<<20)
|
||||
var req request.WebsiteCAImport
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteCAService.Import(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -182,3 +182,12 @@ type WebsiteSSLFileUpload struct {
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteCAImport struct {
|
||||
Name string `json:"name" validate:"required,max=128"`
|
||||
Type string `json:"type" validate:"required,oneof=paste local"`
|
||||
Certificate string `json:"certificate" validate:"required_if=Type paste"`
|
||||
PrivateKey string `json:"privateKey" validate:"required_if=Type paste"`
|
||||
CertificatePath string `json:"certificatePath" validate:"required_if=Type local"`
|
||||
PrivateKeyPath string `json:"privateKeyPath" validate:"required_if=Type local"`
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
@@ -18,25 +22,27 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssl"
|
||||
"github.com/go-acme/lego/v5/certcrypto"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WebsiteCAService struct {
|
||||
}
|
||||
|
||||
type IWebsiteCAService interface {
|
||||
Import(req request.WebsiteCAImport) error
|
||||
Upload(name string, archive []byte) error
|
||||
Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error)
|
||||
Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error)
|
||||
GetCA(id uint) (*response.WebsiteCADTO, error)
|
||||
@@ -334,7 +340,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
websiteSSL.ExpireDate = cert.NotAfter
|
||||
websiteSSL.StartDate = cert.NotBefore
|
||||
websiteSSL.Type = cert.Issuer.CommonName
|
||||
websiteSSL.Organization = rootCsr.Subject.Organization[0]
|
||||
websiteSSL.Organization = strings.Join(rootCsr.Subject.Organization, ",")
|
||||
websiteSSL.Status = constant.SSLReady
|
||||
|
||||
if req.Renew {
|
||||
@@ -442,3 +448,150 @@ func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) {
|
||||
}
|
||||
return os.Open(path.Join(dir, fileName))
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Upload(name string, archive []byte) error {
|
||||
ca, err := parseCAArchive(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return saveImportedCA(name, ca)
|
||||
}
|
||||
|
||||
func saveImportedCA(name string, ca *model.WebsiteCA) error {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || len(name) > 128 || name == "." || name == ".." || strings.ContainsAny(name, "/\\\x00") {
|
||||
return buserr.WithDetail("ErrInvalidParams", "name", nil)
|
||||
}
|
||||
exist, err := websiteCARepo.GetFirst(repo.WithByName(name))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if exist.ID > 0 {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
ca.Name = name
|
||||
return websiteCARepo.Create(context.Background(), ca)
|
||||
}
|
||||
|
||||
// Read only the certificate and key in memory; never extract user-supplied paths.
|
||||
func parseCAArchive(archive []byte) (*model.WebsiteCA, error) {
|
||||
invalid := func() (*model.WebsiteCA, error) { return nil, buserr.New("ErrCAUpload") }
|
||||
if len(archive) > 10<<20 {
|
||||
return invalid()
|
||||
}
|
||||
reader, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
|
||||
if err != nil || len(reader.File) > 16 {
|
||||
return invalid()
|
||||
}
|
||||
contents := make(map[string][]byte)
|
||||
for _, file := range reader.File {
|
||||
if file.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
name := path.Base(file.Name)
|
||||
if (name != "ca.crt" && name != "ca.key") || contents[name] != nil || file.UncompressedSize64 > 1<<20 {
|
||||
return invalid()
|
||||
}
|
||||
stream, err := file.Open()
|
||||
if err != nil {
|
||||
return invalid()
|
||||
}
|
||||
data, err := io.ReadAll(io.LimitReader(stream, (1<<20)+1))
|
||||
stream.Close()
|
||||
if err != nil || len(data) > 1<<20 {
|
||||
return invalid()
|
||||
}
|
||||
contents[name] = data
|
||||
}
|
||||
return parseAndValidateRootCA(contents["ca.crt"], contents["ca.key"])
|
||||
}
|
||||
|
||||
func parseAndValidateRootCA(certificate, privateKey []byte) (*model.WebsiteCA, error) {
|
||||
invalid := func() (*model.WebsiteCA, error) { return nil, buserr.New("ErrCAImport") }
|
||||
if len(certificate) > 1<<20 || len(privateKey) > 1<<20 {
|
||||
return invalid()
|
||||
}
|
||||
pair, err := tls.X509KeyPair(certificate, privateKey)
|
||||
if err != nil || len(pair.Certificate) != 1 {
|
||||
return invalid()
|
||||
}
|
||||
cert := pair.Leaf
|
||||
now := time.Now()
|
||||
if cert == nil || !cert.IsCA || !cert.BasicConstraintsValid || cert.KeyUsage&x509.KeyUsageCertSign == 0 ||
|
||||
!bytes.Equal(cert.RawIssuer, cert.RawSubject) || cert.CheckSignatureFrom(cert) != nil ||
|
||||
now.Before(cert.NotBefore) || !now.Before(cert.NotAfter) {
|
||||
return invalid()
|
||||
}
|
||||
ca := &model.WebsiteCA{CSR: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))}
|
||||
// Normalize keys to the formats used by ObtainSSL, including PKCS#8 uploads.
|
||||
switch key := pair.PrivateKey.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
bits := key.Curve.Params().BitSize
|
||||
if bits != 256 && bits != 384 {
|
||||
return invalid()
|
||||
}
|
||||
der, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return invalid()
|
||||
}
|
||||
ca.KeyType = fmt.Sprintf("EC%d", bits)
|
||||
ca.PrivateKey = string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der}))
|
||||
case *rsa.PrivateKey:
|
||||
bits := key.N.BitLen()
|
||||
if bits != 2048 && bits != 3072 && bits != 4096 && bits != 8192 {
|
||||
return invalid()
|
||||
}
|
||||
ca.KeyType = fmt.Sprintf("RSA%d", bits)
|
||||
ca.PrivateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
|
||||
default:
|
||||
return invalid()
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
func (w WebsiteCAService) Import(req request.WebsiteCAImport) error {
|
||||
certificate, privateKey := []byte(req.Certificate), []byte(req.PrivateKey)
|
||||
switch req.Type {
|
||||
case "local":
|
||||
var err error
|
||||
certificate, err = readCAImportFile(req.CertificatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privateKey, err = readCAImportFile(req.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "paste":
|
||||
default:
|
||||
return buserr.New("ErrCAImport")
|
||||
}
|
||||
ca, err := parseAndValidateRootCA(certificate, privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return saveImportedCA(req.Name, ca)
|
||||
}
|
||||
|
||||
func readCAImportFile(name string) ([]byte, error) {
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !info.Mode().IsRegular() || info.Size() > 1<<20 {
|
||||
return nil, buserr.New("ErrCAImport")
|
||||
}
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
content, err := io.ReadAll(io.LimitReader(file, (1<<20)+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(content) > 1<<20 {
|
||||
return nil, buserr.New("ErrCAImport")
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [
|
||||
VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Restaurar instantánea [{{ .snapshot }}] para la máquin
|
||||
VMSnapshotRecoverStep: "Restaurar instantánea de máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Eliminar instantánea [{{ .snapshot }}] de la máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Eliminar instantánea de máquina virtual [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -774,3 +774,7 @@ VMSnapshotRecoverTask: "بازیابی اسنپشات [{{ .snapshot }}] بر
|
||||
VMSnapshotRecoverStep: "بازیابی اسنپشات ماشین مجازی [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "حذف اسنپشات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "حذف اسنپشات ماشین مجازی [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "仮想マシン [{{ .vm }}] のスナップショット
|
||||
VMSnapshotRecoverStep: "仮想マシンのスナップショット [{{ .snapshot }}] を復元"
|
||||
VMSnapshotDeleteTask: "仮想マシン [{{ .vm }}] のスナップショット [{{ .snapshot }}] を削除"
|
||||
VMSnapshotDeleteStep: "仮想マシンのスナップショット [{{ .snapshot }}] を削除"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}]
|
||||
VMSnapshotRecoverStep: "가상 머신 스냅샷 [{{ .snapshot }}] 복구"
|
||||
VMSnapshotDeleteTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] 삭제"
|
||||
VMSnapshotDeleteStep: "가상 머신 스냅샷 [{{ .snapshot }}] 삭제"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -764,3 +764,7 @@ VMSnapshotRecoverTask: "ກູ້ຄືນສະແນັບຊັອດ [{{ .sn
|
||||
VMSnapshotRecoverStep: "ກູ້ຄືນສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "ລຶບສະແນັບຊັອດ [{{ .snapshot }}] ຂອງເຄື່ອງສະເໝືອນ [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "ລຶບສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Pulihkan syot kilat [{{ .snapshot }}] untuk mesin maya [
|
||||
VMSnapshotRecoverStep: "Pulihkan syot kilat mesin maya [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Padam syot kilat [{{ .snapshot }}] untuk mesin maya [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Padam syot kilat mesin maya [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Restaurar snapshot [{{ .snapshot }}] da máquina virtual
|
||||
VMSnapshotRecoverStep: "Restaurar snapshot da máquina virtual [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Excluir snapshot [{{ .snapshot }}] da máquina virtual [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Excluir snapshot da máquina virtual [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Восстановить снимок [{{ .snapshot }}]
|
||||
VMSnapshotRecoverStep: "Восстановить снимок виртуальной машины [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "Удалить снимок [{{ .snapshot }}] виртуальной машины [{{ .vm }}]"
|
||||
VMSnapshotDeleteStep: "Удалить снимок виртуальной машины [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snaps
|
||||
VMSnapshotRecoverStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] geri yükle"
|
||||
VMSnapshotDeleteTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snapshot }}] sil"
|
||||
VMSnapshotDeleteStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] sil"
|
||||
|
||||
ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm."
|
||||
|
||||
ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm."
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "復原虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverStep: "復原虛擬機快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "刪除虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteStep: "刪除虛擬機快照 [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "請上傳包含有效自簽根憑證 ca.crt 及匹配私鑰 ca.key 的 ZIP 檔案(最大 10 MB),金鑰演算法須為支援的 RSA 或 EC。"
|
||||
|
||||
ErrCAImport: "請提供有效的自簽根憑證及匹配私鑰(每個最大 1 MB),金鑰演算法須為支援的 RSA 或 EC。"
|
||||
|
||||
@@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "恢复虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotRecoverStep: "恢复虚拟机快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteTask: "删除虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]"
|
||||
VMSnapshotDeleteStep: "删除虚拟机快照 [{{ .snapshot }}]"
|
||||
|
||||
ErrCAUpload: "请上传包含有效自签根证书 ca.crt 及匹配私钥 ca.key 的 ZIP 文件(最大 10 MB),密钥算法须为支持的 RSA 或 EC。"
|
||||
|
||||
ErrCAImport: "请提供有效的自签根证书及匹配私钥(每个最大 1 MB),密钥算法须为支持的 RSA 或 EC。"
|
||||
|
||||
@@ -13,6 +13,8 @@ func (a *WebsiteCARouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
groupRouter.POST("/import", baseApi.ImportWebsiteCA)
|
||||
groupRouter.POST("/upload", baseApi.UploadWebsiteCA)
|
||||
groupRouter.POST("/search", baseApi.PageWebsiteCA)
|
||||
groupRouter.POST("", baseApi.CreateWebsiteCA)
|
||||
groupRouter.POST("/del", baseApi.DeleteWebsiteCA)
|
||||
|
||||
@@ -626,6 +626,9 @@ export namespace Website {
|
||||
}
|
||||
|
||||
export interface SSLObtainByCA {
|
||||
autoRenew: boolean;
|
||||
execShell: boolean;
|
||||
shell: string;
|
||||
id: number;
|
||||
domains: string;
|
||||
keyType: string;
|
||||
|
||||
@@ -451,3 +451,18 @@ export const deleteTemplateOutput = (params: { id: number }) => {
|
||||
export const getTemplateOutput = (id: number) => {
|
||||
return http.post<Website.TemplateOutputDTO>(`/websites/templates/outputs/get`, { id });
|
||||
};
|
||||
|
||||
export const uploadCA = (params: FormData) => {
|
||||
return http.upload(`/websites/ca/upload`, params);
|
||||
};
|
||||
|
||||
export const importCA = (params: {
|
||||
name: string;
|
||||
type: 'paste' | 'local';
|
||||
certificate?: string;
|
||||
privateKey?: string;
|
||||
certificatePath?: string;
|
||||
privateKeyPath?: string;
|
||||
}) => {
|
||||
return http.post(`/websites/ca/import`, params);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<DialogPro v-model="open" :title="form.title" size="small" @close="handleClose">
|
||||
<component
|
||||
:is="drawer ? DrawerPro : DialogPro"
|
||||
v-model="open"
|
||||
v-bind="drawer ? { header: form.title, size: 'large' } : { title: form.title, size: 'small' }"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="del-dialog-body">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
@@ -30,11 +35,14 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DialogPro from '@/components/dialog-pro/index.vue';
|
||||
import DrawerPro from '@/components/drawer-pro/index.vue';
|
||||
withDefaults(defineProps<{ drawer?: boolean }>(), { drawer: false });
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
@@ -4376,6 +4376,7 @@ const message = {
|
||||
'Enabling this can avoid certificate leakage, setting a default website will invalidate this setting',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm:
|
||||
'The following certificates will be deleted. ACME certificates will be automatically revoked. Continue?',
|
||||
deleteAndRevoke: 'Delete and revoke',
|
||||
|
||||
@@ -4429,6 +4429,7 @@ const message = {
|
||||
'Habilitar esto puede evitar la fuga de certificados, establecer un sitio web predeterminado invalidará esta configuración',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm:
|
||||
'Se eliminarán los siguientes certificados. Los certificados ACME se revocarán automáticamente. ¿Desea continuar?',
|
||||
deleteAndRevoke: 'Eliminar y revocar',
|
||||
|
||||
@@ -4338,6 +4338,7 @@ const message = {
|
||||
'فعالسازی این گزینه میتواند از نشت گواهی جلوگیری کند، تنظیم یک وبسایت پیشفرض این تنظیم را بیاثر میکند',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm: 'گواهیهای زیر حذف خواهند شد. گواهیهای ACME بهطور خودکار باطل خواهند شد. آیا ادامه میدهید؟',
|
||||
deleteAndRevoke: 'حذف و ابطال',
|
||||
create: 'درخواست',
|
||||
|
||||
@@ -4361,6 +4361,7 @@ const message = {
|
||||
'有効にすると証明書の漏洩を防げますが、デフォルト Web サイトを設定するとこの設定は無効になります',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm: '以下の証明書を削除します。ACME 証明書は自動的に失効されます。続行しますか?',
|
||||
deleteAndRevoke: '削除して失効',
|
||||
create: 'リクエスト',
|
||||
|
||||
@@ -4281,6 +4281,7 @@ const message = {
|
||||
'활성화하면 인증서 누출을 방지할 수 있지만, 기본 웹사이트를 설정하면 이 설정이 무효화됩니다',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm: '다음 인증서를 삭제합니다. ACME 인증서는 자동으로 폐기됩니다. 계속하시겠습니까?',
|
||||
deleteAndRevoke: '삭제 및 폐기',
|
||||
create: '요청',
|
||||
|
||||
@@ -4250,6 +4250,7 @@ const message = {
|
||||
'ການເປີດໃຊ້ສິ່ງນີ້ສາມາດຫຼີກລ່ຽງການຮົ່ວໄຫຼຂອງໃບຢັ້ງຢືນ, ແຕ່ການຕັ້ງເວັບໄຊເລີ່ມຕົ້ນຈະເຮັດໃຫ້ການຕັ້ງຄ່ານີ້ບໍ່ມີຜົນ',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm: 'ຈະລຶບໃບຢັ້ງຢືນຕໍ່ໄປນີ້. ໃບຢັ້ງຢືນ ACME ຈະຖືກຖອນໂດຍອັດຕະໂນມັດ. ຕ້ອງການສືບຕໍ່ບໍ?',
|
||||
deleteAndRevoke: 'ລຶບ ແລະ ຖອນ',
|
||||
create: 'ຮ້ອງຂໍ',
|
||||
|
||||
@@ -4443,6 +4443,7 @@ const message = {
|
||||
'Mengaktifkan ini boleh mengelakkan kebocoran sijil, menetapkan laman web lalai akan membatalkan tetapan ini',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm: 'Sijil berikut akan dipadamkan. Sijil ACME akan dibatalkan secara automatik. Teruskan?',
|
||||
deleteAndRevoke: 'Padam dan batalkan',
|
||||
create: 'Permintaan',
|
||||
|
||||
@@ -4461,6 +4461,7 @@ const message = {
|
||||
'Ativar isso pode evitar vazamento de certificados, definir um site padrão invalidará esta configuração',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm:
|
||||
'Os certificados a seguir serão excluídos. Os certificados ACME serão revogados automaticamente. Continuar?',
|
||||
deleteAndRevoke: 'Excluir e revogar',
|
||||
|
||||
@@ -4433,6 +4433,7 @@ const message = {
|
||||
'Включение этого может предотвратить утечку сертификатов, установка веб-сайта по умолчанию сделает эту настройку недействительной',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm:
|
||||
'Следующие сертификаты будут удалены. Сертификаты ACME будут автоматически отозваны. Продолжить?',
|
||||
deleteAndRevoke: 'Удалить и отозвать',
|
||||
|
||||
@@ -4447,6 +4447,7 @@ const message = {
|
||||
'Etkinleştirilmesi sertifika sızıntısını önleyebilir, varsayılan bir web sitesi ayarlamak bu ayarı geçersiz kılar',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: 'Upload the downloaded ZIP containing ca.crt and ca.key (up to 10 MB).',
|
||||
deleteConfirm:
|
||||
'Aşağıdaki sertifikalar silinecek. ACME sertifikaları otomatik olarak iptal edilecek. Devam edilsin mi?',
|
||||
deleteAndRevoke: 'Sil ve iptal et',
|
||||
|
||||
@@ -4080,6 +4080,7 @@ const message = {
|
||||
sslRejectHandshakeHelper: '開啟之後可以避免憑證洩露,設置預設網站會讓此設置失效',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: '上傳下載的自簽根憑證 ZIP 檔案,包含 ca.crt 和 ca.key,最大 10 MB。',
|
||||
deleteConfirm: '將對以下憑證進行 刪除 操作,ACME 類型憑證會被自動撤銷,是否繼續?',
|
||||
deleteAndRevoke: '刪除並撤銷',
|
||||
create: '申請憑證',
|
||||
|
||||
@@ -4125,6 +4125,7 @@ const message = {
|
||||
sslRejectHandshakeHelper: '开启之后可以避免证书泄露,设置默认网站会让此设置失效',
|
||||
},
|
||||
ssl: {
|
||||
caUploadHelp: '上传下载的自签根证书 ZIP 文件,包含 ca.crt 和 ca.key,最大 10 MB。',
|
||||
deleteConfirm: '将对以下证书进行 删除 操作,ACME类型证书会被自动吊销,是否继续?',
|
||||
deleteAndRevoke: '删除并吊销',
|
||||
create: '申请证书',
|
||||
|
||||
@@ -43,6 +43,42 @@ const webSiteRouter = {
|
||||
permission: 'website_cert_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/websites/ssl/ca',
|
||||
name: 'SSL-CA',
|
||||
component: () => import('@/views/website/ssl/ca/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/websites/ssl',
|
||||
title: 'menu.ssl',
|
||||
detail: 'ssl.selfSigned',
|
||||
permission: 'website_cert_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/websites/ssl/acme-account',
|
||||
name: 'SSL-ACME',
|
||||
component: () => import('@/views/website/ssl/acme-account/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/websites/ssl',
|
||||
title: 'menu.ssl',
|
||||
detail: 'website.acmeAccountManage',
|
||||
permission: 'website_cert_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/websites/ssl/dns-account',
|
||||
name: 'SSL-DNS',
|
||||
component: () => import('@/views/website/ssl/dns-account/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/websites/ssl',
|
||||
title: 'menu.ssl',
|
||||
detail: 'website.dnsAccountManage',
|
||||
permission: 'website_cert_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/websites/templates',
|
||||
name: 'WebsiteTemplate',
|
||||
|
||||
@@ -1,78 +1,81 @@
|
||||
<template>
|
||||
<DialogPro v-model="open" :title="$t('commons.button.create')" size="small" @close="handleClose">
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form @submit.prevent ref="accountForm" label-position="top" :model="account" :rules="rules">
|
||||
<el-form-item :label="$t('website.email')" prop="email">
|
||||
<el-input v-model.trim="account.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.useProxy')" prop="useProxy" v-if="isProductPro">
|
||||
<el-switch v-model="account.useProxy"></el-switch>
|
||||
<span class="input-help">
|
||||
{{ $t('website.useProxyHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.acmeAccountType')" prop="type">
|
||||
<el-select v-model="account.type">
|
||||
<el-option
|
||||
v-for="(acme, index) in AcmeAccountTypes"
|
||||
:key="index"
|
||||
:label="acme.label"
|
||||
:value="acme.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help" v-if="account.type === 'buypass'">
|
||||
{{ $t('ssl.buypassHelper') }}
|
||||
</span>
|
||||
<span class="input-help" v-if="account.type == 'google'">
|
||||
{{ $t('ssl.googleCloudHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.keyType')" prop="keyType">
|
||||
<el-select v-model="account.keyType">
|
||||
<el-option
|
||||
v-for="(keyType, index) in KeyTypes"
|
||||
:key="index"
|
||||
:label="keyType.label"
|
||||
:value="keyType.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="account.type == 'custom'">
|
||||
<el-form-item :label="$t('ssl.customAcmeURL')" prop="caDirURL">
|
||||
<el-input v-model.trim="account.caDirURL"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.useEAB')" prop="useEAB">
|
||||
<el-switch v-model="account.useEAB"></el-switch>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<DrawerPro v-model="open" :header="$t('commons.button.create')" size="large" @close="handleClose">
|
||||
<el-form
|
||||
v-loading="loading"
|
||||
@submit.prevent
|
||||
ref="accountForm"
|
||||
label-position="top"
|
||||
:model="account"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item :label="$t('website.email')" prop="email">
|
||||
<el-input v-model.trim="account.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.useProxy')" prop="useProxy" v-if="isProductPro">
|
||||
<el-switch v-model="account.useProxy"></el-switch>
|
||||
<span class="input-help">
|
||||
{{ $t('website.useProxyHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.acmeAccountType')" prop="type">
|
||||
<el-select v-model="account.type">
|
||||
<el-option
|
||||
v-for="(acme, index) in AcmeAccountTypes"
|
||||
:key="index"
|
||||
:label="acme.label"
|
||||
:value="acme.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help" v-if="account.type === 'buypass'">
|
||||
{{ $t('ssl.buypassHelper') }}
|
||||
</span>
|
||||
<span class="input-help" v-if="account.type == 'google'">
|
||||
{{ $t('ssl.googleCloudHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.keyType')" prop="keyType">
|
||||
<el-select v-model="account.keyType">
|
||||
<el-option
|
||||
v-for="(keyType, index) in KeyTypes"
|
||||
:key="index"
|
||||
:label="keyType.label"
|
||||
:value="keyType.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="account.type == 'custom'">
|
||||
<el-form-item :label="$t('ssl.customAcmeURL')" prop="caDirURL">
|
||||
<el-input v-model.trim="account.caDirURL"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.useEAB')" prop="useEAB">
|
||||
<el-switch v-model="account.useEAB"></el-switch>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
account.type == 'google' ||
|
||||
account.type == 'freessl' ||
|
||||
(account.type === 'custom' && account.useEAB)
|
||||
"
|
||||
>
|
||||
<el-form-item label="EAB kid" prop="eabKid">
|
||||
<el-input v-model.trim="account.eabKid"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="EAB HmacKey" prop="eabHmacKey">
|
||||
<el-input type="textarea" :rows="3" v-model.trim="account.eabHmacKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-link
|
||||
v-if="account.type == 'google'"
|
||||
class="ml-1.5"
|
||||
type="primary"
|
||||
target="_blank"
|
||||
href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial?hl=zh-cn"
|
||||
>
|
||||
{{ $t('ssl.googleHelper') }}
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div
|
||||
v-if="
|
||||
account.type == 'google' ||
|
||||
account.type == 'freessl' ||
|
||||
(account.type === 'custom' && account.useEAB)
|
||||
"
|
||||
>
|
||||
<el-form-item label="EAB kid" prop="eabKid">
|
||||
<el-input v-model.trim="account.eabKid"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="EAB HmacKey" prop="eabHmacKey">
|
||||
<el-input type="textarea" :rows="3" v-model.trim="account.eabHmacKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-link
|
||||
v-if="account.type == 'google'"
|
||||
class="ml-1.5"
|
||||
type="primary"
|
||||
target="_blank"
|
||||
href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial?hl=zh-cn"
|
||||
>
|
||||
{{ $t('ssl.googleHelper') }}
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
@@ -81,7 +84,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
@@ -118,16 +121,15 @@ const initData = () => ({
|
||||
});
|
||||
|
||||
const account = ref(initData());
|
||||
const em = defineEmits(['close']);
|
||||
const em = defineEmits(['saved']);
|
||||
|
||||
const handleClose = () => {
|
||||
resetForm();
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
accountForm.value.resetFields();
|
||||
accountForm.value?.resetFields();
|
||||
account.value = initData();
|
||||
};
|
||||
|
||||
@@ -136,22 +138,17 @@ const acceptParams = () => {
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
createAcmeAccount(account.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
if (!formEl || loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
if (!(await formEl.validate().catch(() => false))) return;
|
||||
await createAcmeAccount(account.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
em('saved');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('website.acmeAccountManage')" size="large" @close="handleClose">
|
||||
<template #content>
|
||||
<div class="mb-1.5">
|
||||
<div>
|
||||
<RouterButton :buttons="routerButton" />
|
||||
<LayoutContent backName="SSL" :title="$t('website.acmeAccountManage')">
|
||||
<template #leftToolBar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #prompt>
|
||||
<el-alert :title="$t('ssl.acmeHelper')" type="info" :closable="false" />
|
||||
</div>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table-column
|
||||
:label="$t('website.email')"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
prop="email"
|
||||
min-width="100px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.acmeAccountType')" fix show-overflow-tooltip prop="type">
|
||||
<template #default="{ row }">
|
||||
{{ getAccountName(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.useProxy')" min-width="100px" v-if="isProductPro">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-permission v-model="row.useProxy" @change="update(row)"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="URL" show-overflow-tooltip prop="url" min-width="300px"></el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="1"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
<Create ref="createRef" @close="search()"></Create>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
|
||||
<el-table-column
|
||||
:label="$t('website.email')"
|
||||
fix
|
||||
show-overflow-tooltip
|
||||
prop="email"
|
||||
min-width="100px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.acmeAccountType')" fix show-overflow-tooltip prop="type">
|
||||
<template #default="{ row }">
|
||||
{{ getAccountName(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.useProxy')" min-width="100px" v-if="isProductPro">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-permission v-model="row.useProxy" @change="update(row)"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="URL" show-overflow-tooltip prop="url" min-width="300px"></el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="1"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<Create ref="createRef" @saved="search()" />
|
||||
<OpDialog drawer ref="opRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -52,14 +55,14 @@ import { readCachedPageSize } from '@/utils/panel-cache';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { deleteAcmeAccount, searchAcmeAccount, updateAcmeAccount } from '@/api/modules/website';
|
||||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { onMounted, reactive, ref, toRef } from 'vue';
|
||||
import Create from './create/index.vue';
|
||||
import { getAccountName, getKeyName } from '@/utils/ssl';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isProductPro } = useGlobalStore();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const data = ref();
|
||||
const createRef = ref();
|
||||
@@ -81,11 +84,6 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const req = {
|
||||
page: paginationConfig.currentPage,
|
||||
@@ -108,10 +106,6 @@ const openCreate = () => {
|
||||
createRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const deleteAccount = async (row: any) => {
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
@@ -125,7 +119,9 @@ const deleteAccount = async (row: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
const routerButton = [{ label: i18n.global.t('website.acmeAccountManage'), path: '/websites/ssl/acme-account' }];
|
||||
useSearchReset({ page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,42 +1,38 @@
|
||||
<template>
|
||||
<DialogPro v-model="open" :title="$t('commons.button.create')" @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('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>
|
||||
<DrawerPro v-model="open" size="large" :header="$t('commons.button.create')" @close="handleClose">
|
||||
<el-form v-loading="loading" @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('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>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
@@ -45,7 +41,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -90,7 +86,7 @@ const handleClose = () => {
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
caForm.value.resetFields();
|
||||
caForm.value?.resetFields();
|
||||
ca.value = initData();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('ssl.selfSigned')" size="large" @close="handleClose">
|
||||
<template #content>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name"></el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" show-overflow-tooltip prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="3"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
width="250px"
|
||||
/>
|
||||
</ComplexTable>
|
||||
<Create ref="createRef" @close="search()" />
|
||||
<Obtain ref="obtainRef" @close="search()" />
|
||||
<Detail ref="detailRef" />
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
<div>
|
||||
<RouterButton :buttons="routerButton" />
|
||||
<LayoutContent backName="SSL" :title="$t('ssl.selfSigned')">
|
||||
<template #leftToolBar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
<el-button v-permission type="primary" plain @click="uploadRef?.acceptParams()">
|
||||
{{ $t('commons.button.upload') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
prop="name"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('website.keyType')" show-overflow-tooltip prop="keyType">
|
||||
<template #default="{ row }">
|
||||
{{ getKeyName(row.keyType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="3"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
width="250px"
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<Upload ref="uploadRef" @uploaded="search()" />
|
||||
<Create ref="createRef" @close="search()" />
|
||||
<Detail ref="detailRef" />
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -40,17 +50,19 @@ import { readCachedPageSize } from '@/utils/panel-cache';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { deleteCA, searchCAs, downloadCAFile } from '@/api/modules/website';
|
||||
import i18n from '@/lang';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onMounted, reactive, ref, toRef } from 'vue';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import Create from './create/index.vue';
|
||||
import Upload from './upload/index.vue';
|
||||
import Detail from './detail/index.vue';
|
||||
import { getKeyName } from '@/utils/ssl';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import Obtain from './obtain/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
const routerButton = [{ label: i18n.global.t('ssl.selfSigned'), path: '/websites/ssl/ca' }];
|
||||
const loading = ref(false);
|
||||
const data = ref();
|
||||
const createRef = ref();
|
||||
const uploadRef = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'ca-page-size',
|
||||
currentPage: 1,
|
||||
@@ -58,18 +70,9 @@ const paginationConfig = reactive({
|
||||
total: 0,
|
||||
});
|
||||
const opRef = ref();
|
||||
const obtainRef = ref();
|
||||
const em = defineEmits(['close']);
|
||||
const detailRef = ref();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('ssl.selfSign'),
|
||||
permission: true,
|
||||
click: function (row: Website.CA) {
|
||||
obtain(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('ssl.detail'),
|
||||
click: function (row: Website.CA) {
|
||||
@@ -91,14 +94,7 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const obtain = (row: any) => {
|
||||
obtainRef.value.acceptParams(row.id);
|
||||
};
|
||||
useSearchReset({ page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async () => {
|
||||
const req = {
|
||||
@@ -115,11 +111,6 @@ 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'),
|
||||
@@ -144,13 +135,14 @@ const onDownload = (row: Website.CA) => {
|
||||
a.download = row.name + '.zip';
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<DialogPro v-model="open" :title="$t('ssl.selfSigned')" @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"
|
||||
:rows="4"
|
||||
v-model="obtain.domains"
|
||||
:placeholder="$t('ssl.domainHelper')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.remark')" prop="description">
|
||||
<el-input v-model="obtain.description"></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-item :label="''" prop="autoRenew">
|
||||
<el-checkbox v-model="obtain.autoRenew" :label="$t('ssl.autoRenew')" />
|
||||
</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-button
|
||||
icon="Folder"
|
||||
class="mb-2"
|
||||
@click="
|
||||
fileRef.acceptParams({
|
||||
path: obtain.dir.split('\n')[0],
|
||||
dir: true,
|
||||
multiple: true,
|
||||
selectedPaths: obtain.dir.split('\n').filter(Boolean),
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ $t('ssl.dir') }}
|
||||
</el-button>
|
||||
<el-input v-model.trim="obtain.dir" type="textarea" :rows="3" />
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.pushDirHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="execShell">
|
||||
<el-checkbox v-model="obtain.execShell" :label="$t('ssl.execShell')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.shell')" prop="shell" v-if="obtain.execShell">
|
||||
<el-input type="textarea" :rows="4" v-model="obtain.shell" />
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.shellHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE"
|
||||
:push-node="obtain.pushNode"
|
||||
:nodes="obtain.pushNodes"
|
||||
type="ssl"
|
||||
@update:push-node="obtain.pushNode = $event"
|
||||
@update:nodes="obtain.pushNodes = $event"
|
||||
/>
|
||||
</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 v-permission type="primary" @click="submit(obtainForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
<FileList ref="fileRef" @choose="getPath" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { obtainSSLByCA } from '@/api/modules/website';
|
||||
import { Rules, checkNumberRange, checkMaxLength } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMaster, isXpackOrEE } = useGlobalStore();
|
||||
|
||||
const PushToNode = defineAsyncComponent(async () => {
|
||||
const modules = import.meta.glob('@/xpack/views/ssl/index.vue');
|
||||
const loader = modules['/src/xpack/views/ssl/index.vue'];
|
||||
if (loader) {
|
||||
return ((await loader()) as any).default;
|
||||
}
|
||||
return { template: '<div></div>' };
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
const fileRef = ref();
|
||||
const loading = ref(false);
|
||||
const obtainForm = ref<FormInstance>();
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const rules = ref({
|
||||
keyType: [Rules.requiredSelect],
|
||||
domains: [Rules.requiredInput],
|
||||
dir: [Rules.requiredInput],
|
||||
time: [Rules.integerNumber, checkNumberRange(1, 10000)],
|
||||
shell: [Rules.requiredInput],
|
||||
description: [checkMaxLength(128)],
|
||||
pushNodes: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const initData = () => ({
|
||||
keyType: 'EC256',
|
||||
domains: '',
|
||||
id: 0,
|
||||
time: 10,
|
||||
unit: 'year',
|
||||
pushDir: false,
|
||||
dir: '',
|
||||
autoRenew: true,
|
||||
description: '',
|
||||
execShell: false,
|
||||
shell: '',
|
||||
pushNode: false,
|
||||
pushNodes: [] as string[],
|
||||
nodes: '',
|
||||
});
|
||||
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 getPath = (dirs: string[]) => {
|
||||
obtain.value.dir = dirs.join('\n');
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
obtain.value.nodes = obtain.value.pushNode ? obtain.value.pushNodes.join(',') : '';
|
||||
|
||||
obtainSSLByCA(obtain.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" size="large" :header="$t('commons.button.upload')" @close="handleClose">
|
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" :disabled="loading" @submit.prevent>
|
||||
<el-form-item :label="$t('website.importType')" prop="type">
|
||||
<el-select v-model="form.type" :disabled="loading" @change="changeType">
|
||||
<el-option :label="$t('website.pasteSSL')" :value="'paste'"></el-option>
|
||||
<el-option :label="$t('website.localSSL')" :value="'local'"></el-option>
|
||||
<el-option :label="$t('commons.button.upload') + ' ZIP'" :value="'upload'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.type !== 'upload'" :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="form.name" :maxlength="128" />
|
||||
</el-form-item>
|
||||
<div v-if="form.type === 'paste'">
|
||||
<el-form-item :label="$t('website.certificate')" prop="certificate">
|
||||
<el-input v-model="form.certificate" :rows="6" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.privateKey')" prop="privateKey">
|
||||
<el-input v-model="form.privateKey" :rows="6" type="textarea" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="form.type === 'local'">
|
||||
<el-form-item :label="$t('website.certificatePath')" prop="certificatePath">
|
||||
<el-input v-model="form.certificatePath">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="certFileRef.acceptParams({ dir: false })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.privateKeyPath')" prop="privateKeyPath">
|
||||
<el-input v-model="form.privateKeyPath">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="keyFileRef.acceptParams({ dir: false })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item v-if="form.type === 'upload'" :label="$t('file.file')" prop="file">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
accept=".zip"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:disabled="loading"
|
||||
:on-change="onChange"
|
||||
:on-remove="onRemove"
|
||||
>
|
||||
<el-button type="primary" icon="Upload" :disabled="loading">
|
||||
{{ $t('file.selectFile') }}
|
||||
</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">{{ $t('ssl.caUploadHelp') }}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="loading" @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-permission type="primary" :loading="loading" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<FileList ref="certFileRef" @choose="form.certificatePath = $event" />
|
||||
<FileList ref="keyFileRef" @choose="form.privateKeyPath = $event" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
import type { FormInstance, UploadFile, UploadInstance, UploadRawFile } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { uploadCA, importCA } from '@/api/modules/website';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const certFileRef = ref();
|
||||
const keyFileRef = ref();
|
||||
const initialForm = () => ({
|
||||
name: '',
|
||||
type: 'upload',
|
||||
certificate: '',
|
||||
privateKey: '',
|
||||
certificatePath: '',
|
||||
privateKeyPath: '',
|
||||
file: undefined as UploadRawFile | undefined,
|
||||
});
|
||||
const form = reactive(initialForm());
|
||||
const rules = {
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
type: [Rules.requiredSelect],
|
||||
file: [Rules.requiredSelect],
|
||||
certificate: [Rules.requiredInput],
|
||||
privateKey: [Rules.requiredInput],
|
||||
certificatePath: [Rules.requiredInput],
|
||||
privateKeyPath: [Rules.requiredInput],
|
||||
};
|
||||
const changeType = async () => {
|
||||
form.file = undefined;
|
||||
uploadRef.value?.clearFiles();
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
const emit = defineEmits(['uploaded']);
|
||||
|
||||
const onRemove = () => {
|
||||
form.file = undefined;
|
||||
};
|
||||
const onChange = (file: UploadFile) => {
|
||||
if (!file.raw || !file.name.toLowerCase().endsWith('.zip') || file.raw.size > 10 * 1024 * 1024) {
|
||||
uploadRef.value?.clearFiles();
|
||||
form.file = undefined;
|
||||
MsgError(i18n.global.t('ssl.caUploadHelp'));
|
||||
return;
|
||||
}
|
||||
form.file = file.raw;
|
||||
formRef.value?.clearValidate('file');
|
||||
};
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
const acceptParams = async () => {
|
||||
Object.assign(form, initialForm());
|
||||
open.value = true;
|
||||
await nextTick();
|
||||
uploadRef.value?.clearFiles();
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
const submit = async () => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
if (!(await formRef.value?.validate().catch(() => false))) return;
|
||||
if (form.type === 'upload') {
|
||||
if (!form.file) return;
|
||||
const data = new FormData();
|
||||
data.append('file', form.file);
|
||||
await uploadCA(data);
|
||||
} else {
|
||||
await importCA(
|
||||
form.type === 'paste'
|
||||
? { type: 'paste', name: form.name, certificate: form.certificate, privateKey: form.privateKey }
|
||||
: {
|
||||
type: 'local',
|
||||
name: form.name,
|
||||
certificatePath: form.certificatePath,
|
||||
privateKeyPath: form.privateKeyPath,
|
||||
},
|
||||
);
|
||||
}
|
||||
MsgSuccess(i18n.global.t('commons.msg.uploadSuccess'));
|
||||
handleClose();
|
||||
emit('uploaded');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
||||
@@ -1,7 +1,33 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('ssl.' + operate)" size="large" @close="handleClose">
|
||||
<el-form ref="sslForm" label-position="top" :model="ssl" label-width="100px" :rules="rules" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-form
|
||||
ref="sslForm"
|
||||
label-position="top"
|
||||
:model="ssl"
|
||||
label-width="100px"
|
||||
:rules="rules"
|
||||
:disabled="loading"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.type')" v-if="operate === 'create'">
|
||||
<el-radio-group v-model="certificateType" @change="changeCertificateType">
|
||||
<el-radio-button value="acme">ACME</el-radio-button>
|
||||
<el-radio-button value="selfSigned">{{ $t('ssl.selfSigned') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="ssl.provider === 'selfSigned' && operate === 'create'"
|
||||
:label="$t('ssl.ca')"
|
||||
prop="caId"
|
||||
>
|
||||
<el-select v-model="ssl.caId" filterable :loading="caLoading">
|
||||
<el-option v-for="ca in cas" :key="ca.id" :label="ca.name" :value="ca.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="ssl.provider === 'selfSigned'" :label="$t('website.domain')" prop="domains">
|
||||
<el-input v-model="ssl.domains" type="textarea" :rows="4" :placeholder="$t('ssl.domainHelper')" />
|
||||
</el-form-item>
|
||||
<el-row v-else :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
|
||||
<el-input v-model.trim="ssl.primaryDomain"></el-input>
|
||||
@@ -20,10 +46,14 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item :label="''" prop="autoRenew">
|
||||
<el-form-item :label="''" prop="isIP" v-if="ssl.provider !== 'selfSigned'">
|
||||
<el-checkbox v-model="ssl.isIP" :label="$t('ssl.isIP')" @change="changeIP" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.otherDomains')" prop="otherDomains" v-if="!ssl.isIP">
|
||||
<el-form-item
|
||||
:label="$t('website.otherDomains')"
|
||||
prop="otherDomains"
|
||||
v-if="ssl.provider !== 'selfSigned' && !ssl.isIP"
|
||||
>
|
||||
<el-input type="textarea" :rows="3" v-model="ssl.otherDomains"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.remark')" prop="description">
|
||||
@@ -61,6 +91,20 @@
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="ssl.provider === 'selfSigned' && operate === 'create'"
|
||||
:label="$t('ssl.days')"
|
||||
prop="time"
|
||||
>
|
||||
<el-input type="number" v-model.number="ssl.time">
|
||||
<template #append>
|
||||
<el-select v-model="ssl.unit" style="width: 100px">
|
||||
<el-option :label="$t('commons.units.day')" value="day" />
|
||||
<el-option :label="$t('commons.units.year')" value="year" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.provider')" prop="provider" v-if="ssl.provider != 'selfSigned'">
|
||||
<el-radio-group v-model="ssl.provider" @change="changeProvider()">
|
||||
<el-radio value="dnsAccount" :disabled="ssl.isIP">{{ $t('website.dnsAccount') }}</el-radio>
|
||||
@@ -123,6 +167,8 @@
|
||||
{{ $t('ssl.nameserverHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="ssl.provider !== 'selfSigned' || operate === 'create'">
|
||||
<el-form-item :label="''" prop="pushDir">
|
||||
<el-checkbox v-model="ssl.pushDir" :label="$t('ssl.pushDir')" />
|
||||
</el-form-item>
|
||||
@@ -184,12 +230,20 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { createSSL, listWebsites, searchAcmeAccount, searchDnsAccount, updateSSL } from '@/api/modules/website';
|
||||
import { Rules, checkMaxLength } from '@/global/form-rules';
|
||||
import {
|
||||
createSSL,
|
||||
listWebsites,
|
||||
searchAcmeAccount,
|
||||
searchDnsAccount,
|
||||
updateSSL,
|
||||
searchCAs,
|
||||
obtainSSLByCA,
|
||||
} from '@/api/modules/website';
|
||||
import { Rules, checkMaxLength, checkNumberRange } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
import { getDNSName, getAccountName } from '@/utils/ssl';
|
||||
@@ -232,7 +286,14 @@ const dnsAccounts = ref<Website.DnsAccount[]>();
|
||||
const acmeAccounts = ref<Website.AcmeAccount[]>();
|
||||
const sslForm = ref<FormInstance>();
|
||||
const websites = ref();
|
||||
const rules = ref({
|
||||
const certificateType = ref('acme');
|
||||
const cas = ref<Website.CA[]>([]);
|
||||
const caLoading = ref(false);
|
||||
let caRequestVersion = 0;
|
||||
const rules = computed(() => ({
|
||||
caId: [Rules.requiredSelectBusiness],
|
||||
time: [Rules.requiredInput, Rules.integerNumber, checkNumberRange(1, 10000)],
|
||||
domains: [Rules.requiredInput],
|
||||
primaryDomain: [Rules.requiredInput, Rules.domainOrIP],
|
||||
acmeAccountId: [Rules.requiredSelectBusiness],
|
||||
dnsAccountId: [Rules.requiredSelectBusiness],
|
||||
@@ -245,10 +306,14 @@ const rules = ref({
|
||||
shell: [Rules.requiredInput],
|
||||
description: [checkMaxLength(128)],
|
||||
pushNodes: [Rules.requiredSelect],
|
||||
});
|
||||
}));
|
||||
const websiteID = ref();
|
||||
|
||||
const initData = () => ({
|
||||
domains: '',
|
||||
caId: undefined as number | undefined,
|
||||
time: 10,
|
||||
unit: 'year',
|
||||
id: 0,
|
||||
primaryDomain: '',
|
||||
otherDomains: '',
|
||||
@@ -297,6 +362,10 @@ const resetForm = () => {
|
||||
dnsResolve.value = [];
|
||||
ssl.value = initData();
|
||||
websiteID.value = undefined;
|
||||
certificateType.value = 'acme';
|
||||
cas.value = [];
|
||||
caRequestVersion++;
|
||||
caLoading.value = false;
|
||||
};
|
||||
|
||||
const changeIP = () => {
|
||||
@@ -331,6 +400,9 @@ const acceptParams = (op: string, websiteSSL?: Website.SSLDTO) => {
|
||||
ssl.value.description = websiteSSL.description;
|
||||
ssl.value.id = websiteSSL.id;
|
||||
ssl.value.provider = websiteSSL.provider;
|
||||
ssl.value.domains = [websiteSSL.primaryDomain, ...(websiteSSL.domains || '').split(',')]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
ssl.value.skipDNS = websiteSSL.skipDNS;
|
||||
ssl.value.disableCNAME = websiteSSL.disableCNAME;
|
||||
ssl.value.nameserver1 = websiteSSL.nameserver1;
|
||||
@@ -338,9 +410,6 @@ const acceptParams = (op: string, websiteSSL?: Website.SSLDTO) => {
|
||||
ssl.value.keyType = websiteSSL.keyType;
|
||||
ssl.value.execShell = websiteSSL.execShell;
|
||||
ssl.value.shell = websiteSSL.shell;
|
||||
if (ssl.value.provider == 'selfSigned') {
|
||||
rules.value.primaryDomain = [];
|
||||
}
|
||||
ssl.value.pushNode = websiteSSL.pushNode;
|
||||
if (websiteSSL.nodes != '') {
|
||||
ssl.value.pushNodes = websiteSSL.nodes
|
||||
@@ -357,6 +426,33 @@ const acceptParams = (op: string, websiteSSL?: Website.SSLDTO) => {
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const changeCertificateType = async () => {
|
||||
ssl.value.provider = certificateType.value === 'selfSigned' ? 'selfSigned' : 'dnsAccount';
|
||||
ssl.value.isIP = false;
|
||||
await nextTick();
|
||||
sslForm.value?.clearValidate();
|
||||
if (certificateType.value !== 'selfSigned' || cas.value.length || caLoading.value) return;
|
||||
caLoading.value = true;
|
||||
const requestVersion = ++caRequestVersion;
|
||||
try {
|
||||
const items: Website.CA[] = [];
|
||||
let page = 1;
|
||||
while (true) {
|
||||
const res = await searchCAs({ page, pageSize: 100 });
|
||||
if (requestVersion !== caRequestVersion) return;
|
||||
const batch = res.data.items || [];
|
||||
items.push(...batch);
|
||||
if (!batch.length || items.length >= res.data.total) break;
|
||||
page++;
|
||||
}
|
||||
if (!open.value) return;
|
||||
cas.value = items;
|
||||
ssl.value.caId = items[0]?.id;
|
||||
} finally {
|
||||
if (requestVersion === caRequestVersion) caLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getPath = (dirs: string[]) => {
|
||||
ssl.value.dir = dirs.join('\n');
|
||||
};
|
||||
@@ -404,9 +500,9 @@ const changeWebsite = () => {
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
if (!formEl || loading.value) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
if (!valid || loading.value) {
|
||||
return;
|
||||
}
|
||||
let nodes = '';
|
||||
@@ -416,6 +512,35 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (operate.value == 'create') {
|
||||
ssl.value.nodes = nodes;
|
||||
if (ssl.value.provider === 'selfSigned') {
|
||||
obtainSSLByCA({
|
||||
id: ssl.value.caId!,
|
||||
domains: ssl.value.domains
|
||||
.split('\n')
|
||||
.map((domain) => domain.trim())
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
keyType: ssl.value.keyType,
|
||||
time: ssl.value.time,
|
||||
unit: ssl.value.unit,
|
||||
autoRenew: ssl.value.autoRenew,
|
||||
description: ssl.value.description,
|
||||
pushDir: ssl.value.pushDir,
|
||||
dir: ssl.value.dir,
|
||||
execShell: ssl.value.execShell,
|
||||
shell: ssl.value.shell,
|
||||
pushNode: ssl.value.pushNode,
|
||||
nodes,
|
||||
})
|
||||
.then(() => {
|
||||
handleClose();
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
createSSL(ssl.value)
|
||||
.then((res: any) => {
|
||||
if (ssl.value.provider != 'dnsManual') {
|
||||
@@ -429,6 +554,14 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
});
|
||||
}
|
||||
if (operate.value == 'edit') {
|
||||
if (ssl.value.provider === 'selfSigned') {
|
||||
const domains = ssl.value.domains
|
||||
.split('\n')
|
||||
.map((domain) => domain.trim())
|
||||
.filter(Boolean);
|
||||
ssl.value.primaryDomain = domains[0];
|
||||
ssl.value.otherDomains = domains.slice(1).join('\n');
|
||||
}
|
||||
const sslUpdate = {
|
||||
id: ssl.value.id,
|
||||
primaryDomain: ssl.value.primaryDomain,
|
||||
|
||||
@@ -1,218 +1,201 @@
|
||||
<template>
|
||||
<DialogPro v-model="open" :title="$t('commons.button.' + accountData.mode)" @close="handleClose">
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form ref="accountForm" label-position="top" :model="account" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="account.name"></el-input>
|
||||
</el-form-item>
|
||||
<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 DNSTypes"
|
||||
:key="index"
|
||||
:label="type.deprecated ? `${type.label} (${$t('ssl.dnsPodRemoved')})` : type.label"
|
||||
:value="type.value"
|
||||
:disabled="type.deprecated && accountData.mode === 'create'"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help text-red-500" v-if="account.type === 'DnsPod'">
|
||||
{{ $t('ssl.dnsPodRemovedTip') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="
|
||||
account.type === 'AliYun' ||
|
||||
account.type === 'AliESA' ||
|
||||
account.type === 'HuaweiCloud' ||
|
||||
account.type === 'BaiduCloud' ||
|
||||
account.type === 'AWSRoute53'
|
||||
"
|
||||
>
|
||||
<el-form-item label="Access key" prop="authorization.accessKey">
|
||||
<el-input v-model.trim="account.authorization['accessKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="Region" prop="authorization.region" v-if="account.type === 'HuaweiCloud'">
|
||||
<el-input v-model.trim="account.authorization['region']" :placeholder="'cn-north-1'"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="account.type === 'TencentCloud'">
|
||||
<el-form-item label="Secret ID" prop="authorization.secretID">
|
||||
<el-input v-model.trim="account.authorization['secretID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Volcengine'">
|
||||
<el-form-item label="Access key" prop="authorization.accessKey">
|
||||
<el-input v-model.trim="account.authorization['accessKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'CloudDns'">
|
||||
<el-form-item label="Client ID" prop="authorization.clientID">
|
||||
<el-input v-model.trim="account.authorization['clientID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Email" prop="authorization.email">
|
||||
<el-input v-model.trim="account.authorization['email']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'CloudFlare'">
|
||||
<el-form-item label="EMAIL" prop="authorization.email">
|
||||
<el-input v-model.trim="account.authorization['email']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Token" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
<span class="input-help text-red-500">
|
||||
{{ $t('ssl.cfHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item
|
||||
label="API Key"
|
||||
prop="authorization.apiKey"
|
||||
v-if="
|
||||
account.type === 'NameCheap' ||
|
||||
account.type === 'NameSilo' ||
|
||||
account.type === 'Godaddy' ||
|
||||
account.type === 'RainYun' ||
|
||||
account.type === 'Spaceship' ||
|
||||
account.type === 'Dynadot' ||
|
||||
account.type === 'Dynu'
|
||||
"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API User" prop="authorization.apiUser" v-if="account.type === 'NameCheap'">
|
||||
<el-input v-model.trim="account.authorization['apiUser']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="API Secret"
|
||||
prop="authorization.apiSecret"
|
||||
v-if="account.type === 'Godaddy' || account.type === 'Spaceship' || account.type === 'Dynadot'"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['apiSecret']"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="account.type === 'NameCom'">
|
||||
<el-form-item label="Username" prop="authorization.apiUser">
|
||||
<el-input v-model.trim="account.authorization['apiUser']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Netcup'">
|
||||
<el-form-item label="Customer Number" prop="authorization.customerNumber">
|
||||
<el-input v-model.trim="account.authorization['customerNumber']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Key" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Password" prop="authorization.password">
|
||||
<el-input
|
||||
v-model.trim="account.authorization['password']"
|
||||
type="password"
|
||||
show-password
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div
|
||||
v-if="account.type === 'FreeMyIP' || account.type === 'Vercel' || account.type === 'IonosCloud'"
|
||||
>
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Ionos'">
|
||||
<el-form-item label="API Prefix" prop="authorization.apiPrefix">
|
||||
<el-input v-model.trim="account.authorization['apiPrefix']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Secret" prop="authorization.apiSecret">
|
||||
<el-input v-model.trim="account.authorization['apiSecret']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'ClouDNS'">
|
||||
<el-form-item label="Auth ID" prop="authorization.authID">
|
||||
<el-input v-model.trim="account.authorization['authID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Sub Auth ID" prop="authorization.subAuthID">
|
||||
<el-input v-model.trim="account.authorization['subAuthID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Auth Password" prop="authorization.authPassword">
|
||||
<el-input v-model.trim="account.authorization['authPassword']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'WestCN' || account.type === 'RegRu'">
|
||||
<el-form-item label="Username" prop="authorization.username">
|
||||
<el-input v-model.trim="account.authorization['username']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Ovh'">
|
||||
<el-form-item label="Endpoint" prop="authorization.endpoint" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="AccessToken"
|
||||
prop="authorization.accessToken"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['accessToken']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'AcmeDNS'">
|
||||
<el-form-item label="API BASE" prop="authorization.endpoint" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="BASE URL" prop="authorization.baseURL" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['baseURL']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'AWSRoute53'">
|
||||
<el-form-item label="Region" prop="authorization.region">
|
||||
<el-input
|
||||
v-model.trim="account.authorization['region']"
|
||||
:placeholder="'us-east-1'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('ssl.hostedZoneID')"
|
||||
prop="authorization.endpoint"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'PorkBun'">
|
||||
<el-form-item label="API Key" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Technitium'">
|
||||
<el-form-item label="BASE URL" prop="authorization.baseURL" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['baseURL']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<DrawerPro v-model="open" :header="$t('commons.button.' + accountData.mode)" size="large" @close="handleClose">
|
||||
<el-form v-loading="loading" ref="accountForm" label-position="top" :model="account" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="account.name"></el-input>
|
||||
</el-form-item>
|
||||
<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 DNSTypes"
|
||||
:key="index"
|
||||
:label="type.deprecated ? `${type.label} (${$t('ssl.dnsPodRemoved')})` : type.label"
|
||||
:value="type.value"
|
||||
:disabled="type.deprecated && accountData.mode === 'create'"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help text-red-500" v-if="account.type === 'DnsPod'">
|
||||
{{ $t('ssl.dnsPodRemovedTip') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="
|
||||
account.type === 'AliYun' ||
|
||||
account.type === 'AliESA' ||
|
||||
account.type === 'HuaweiCloud' ||
|
||||
account.type === 'BaiduCloud' ||
|
||||
account.type === 'AWSRoute53'
|
||||
"
|
||||
>
|
||||
<el-form-item label="Access key" prop="authorization.accessKey">
|
||||
<el-input v-model.trim="account.authorization['accessKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="Region" prop="authorization.region" v-if="account.type === 'HuaweiCloud'">
|
||||
<el-input v-model.trim="account.authorization['region']" :placeholder="'cn-north-1'"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="account.type === 'TencentCloud'">
|
||||
<el-form-item label="Secret ID" prop="authorization.secretID">
|
||||
<el-input v-model.trim="account.authorization['secretID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Volcengine'">
|
||||
<el-form-item label="Access key" prop="authorization.accessKey">
|
||||
<el-input v-model.trim="account.authorization['accessKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'CloudDns'">
|
||||
<el-form-item label="Client ID" prop="authorization.clientID">
|
||||
<el-input v-model.trim="account.authorization['clientID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Email" prop="authorization.email">
|
||||
<el-input v-model.trim="account.authorization['email']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'CloudFlare'">
|
||||
<el-form-item label="EMAIL" prop="authorization.email">
|
||||
<el-input v-model.trim="account.authorization['email']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Token" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
<span class="input-help text-red-500">
|
||||
{{ $t('ssl.cfHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item
|
||||
label="API Key"
|
||||
prop="authorization.apiKey"
|
||||
v-if="
|
||||
account.type === 'NameCheap' ||
|
||||
account.type === 'NameSilo' ||
|
||||
account.type === 'Godaddy' ||
|
||||
account.type === 'RainYun' ||
|
||||
account.type === 'Spaceship' ||
|
||||
account.type === 'Dynadot' ||
|
||||
account.type === 'Dynu'
|
||||
"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API User" prop="authorization.apiUser" v-if="account.type === 'NameCheap'">
|
||||
<el-input v-model.trim="account.authorization['apiUser']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="API Secret"
|
||||
prop="authorization.apiSecret"
|
||||
v-if="account.type === 'Godaddy' || account.type === 'Spaceship' || account.type === 'Dynadot'"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['apiSecret']"></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="account.type === 'NameCom'">
|
||||
<el-form-item label="Username" prop="authorization.apiUser">
|
||||
<el-input v-model.trim="account.authorization['apiUser']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Netcup'">
|
||||
<el-form-item label="Customer Number" prop="authorization.customerNumber">
|
||||
<el-input v-model.trim="account.authorization['customerNumber']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Key" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']" type="password" show-password></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'FreeMyIP' || account.type === 'Vercel' || account.type === 'IonosCloud'">
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Ionos'">
|
||||
<el-form-item label="API Prefix" prop="authorization.apiPrefix">
|
||||
<el-input v-model.trim="account.authorization['apiPrefix']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="API Secret" prop="authorization.apiSecret">
|
||||
<el-input v-model.trim="account.authorization['apiSecret']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'ClouDNS'">
|
||||
<el-form-item label="Auth ID" prop="authorization.authID">
|
||||
<el-input v-model.trim="account.authorization['authID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Sub Auth ID" prop="authorization.subAuthID">
|
||||
<el-input v-model.trim="account.authorization['subAuthID']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Auth Password" prop="authorization.authPassword">
|
||||
<el-input v-model.trim="account.authorization['authPassword']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'WestCN' || account.type === 'RegRu'">
|
||||
<el-form-item label="Username" prop="authorization.username">
|
||||
<el-input v-model.trim="account.authorization['username']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" prop="authorization.password">
|
||||
<el-input v-model.trim="account.authorization['password']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Ovh'">
|
||||
<el-form-item label="Endpoint" prop="authorization.endpoint" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="AccessToken" prop="authorization.accessToken" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['accessToken']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'AcmeDNS'">
|
||||
<el-form-item label="API BASE" prop="authorization.endpoint" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="BASE URL" prop="authorization.baseURL" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['baseURL']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'AWSRoute53'">
|
||||
<el-form-item label="Region" prop="authorization.region">
|
||||
<el-input v-model.trim="account.authorization['region']" :placeholder="'us-east-1'"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('ssl.hostedZoneID')"
|
||||
prop="authorization.endpoint"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'PorkBun'">
|
||||
<el-form-item label="API Key" prop="authorization.apiKey">
|
||||
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="authorization.secretKey">
|
||||
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="account.type === 'Technitium'">
|
||||
<el-form-item label="BASE URL" prop="authorization.baseURL" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model.trim="account.authorization['baseURL']"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="authorization.token">
|
||||
<el-input v-model.trim="account.authorization['token']"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
@@ -221,7 +204,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -269,60 +252,44 @@ const account = ref({
|
||||
type: 'AliYun',
|
||||
authorization: {},
|
||||
});
|
||||
const em = defineEmits(['close']);
|
||||
const em = defineEmits(['saved']);
|
||||
|
||||
const handleClose = () => {
|
||||
resetForm();
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
account.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
type: 'AliYun',
|
||||
authorization: {},
|
||||
};
|
||||
accountForm.value?.resetFields();
|
||||
account.value = { id: 0, name: '', type: 'AliYun', authorization: {} };
|
||||
};
|
||||
|
||||
const acceptParams = async (props: AccountProps) => {
|
||||
const acceptParams = (props: AccountProps) => {
|
||||
resetForm();
|
||||
accountData.value.mode = props.mode;
|
||||
if (props.mode === 'edit') {
|
||||
account.value = props.form;
|
||||
account.value = { ...props.form, authorization: { ...props.form.authorization } };
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
if (accountData.value.mode === 'edit') {
|
||||
updateDnsAccount(account.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
createDnsAccount(account.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!formEl || loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
if (!(await formEl.validate().catch(() => false))) return;
|
||||
const save = accountData.value.mode === 'edit' ? updateDnsAccount : createDnsAccount;
|
||||
await save(account.value);
|
||||
MsgSuccess(
|
||||
i18n.global.t(
|
||||
accountData.value.mode === 'edit' ? 'commons.msg.updateSuccess' : 'commons.msg.createSuccess',
|
||||
),
|
||||
);
|
||||
handleClose();
|
||||
em('saved');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -1,40 +1,47 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('website.dnsAccountManage')" size="large" @close="handleClose">
|
||||
<template #content>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
|
||||
<template #toolbar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<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>{{ getDNSName(row.type) }}</span>
|
||||
<el-tooltip v-if="row.type === 'DnsPod'" :content="$t('ssl.dnsPodRemovedTip')" placement="top">
|
||||
<el-tag type="danger" size="small" class="ml-2">
|
||||
{{ $t('ssl.dnsPodRemoved') }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</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()"></Create>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<OpDialog ref="opRef" @search="search" />
|
||||
<div>
|
||||
<RouterButton :buttons="routerButton" />
|
||||
<LayoutContent backName="SSL" :title="$t('website.dnsAccountManage')">
|
||||
<template #leftToolBar>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
|
||||
<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>{{ getDNSName(row.type) }}</span>
|
||||
<el-tooltip
|
||||
v-if="row.type === 'DnsPod'"
|
||||
:content="$t('ssl.dnsPodRemovedTip')"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger" size="small" class="ml-2">
|
||||
{{ $t('ssl.dnsPodRemoved') }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations
|
||||
:ellipsis="1"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<Create ref="createRef" @saved="search()" />
|
||||
<OpDialog drawer ref="opRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -42,8 +49,9 @@ import { readCachedPageSize } from '@/utils/panel-cache';
|
||||
import Create from './create/index.vue';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { deleteDnsAccount, searchDnsAccount } from '@/api/modules/website';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { onMounted, reactive, ref, toRef } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { getDNSName } from '@/utils/ssl';
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'dns-account-page-size',
|
||||
@@ -53,7 +61,6 @@ const paginationConfig = reactive({
|
||||
});
|
||||
let data = ref<Website.DnsAccount[]>();
|
||||
let createRef = ref();
|
||||
let open = ref(false);
|
||||
const opRef = ref();
|
||||
|
||||
const buttons = [
|
||||
@@ -73,15 +80,6 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const acceptParams = () => {
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
const req = {
|
||||
page: paginationConfig.currentPage,
|
||||
@@ -118,7 +116,6 @@ onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
const routerButton = [{ label: i18n.global.t('website.dnsAccountManage'), path: '/websites/ssl/dns-account' }];
|
||||
useSearchReset({ page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
</script>
|
||||
|
||||
@@ -172,15 +172,12 @@
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
<DnsAccount ref="dnsAccountRef" />
|
||||
<AcmeAccount ref="acmeAccountRef" />
|
||||
<Create ref="sslCreateRef" @close="search()" @submit="openLog" />
|
||||
<Detail ref="detailRef" />
|
||||
<SSLUpload ref="sslUploadRef" @close="search()" />
|
||||
<Apply ref="applyRef" @search="search" @submit="openLog" />
|
||||
<OpDialog ref="opRef" @search="search" @cancel="search" />
|
||||
<Log ref="logRef" @close="search()" :heightDiff="220" />
|
||||
<CA ref="caRef" @close="search()" />
|
||||
<Obtain ref="obtainRef" @close="search()" @submit="openLog" />
|
||||
<DrawerPro v-model="pushOpen" :header="$t('commons.button.sync')" size="large" @close="handlePushClose">
|
||||
<el-form
|
||||
@@ -224,9 +221,7 @@
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteSSL, downloadFile, pushSSLToNode, 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 { routerToNameWithQuery } from '@/utils/router';
|
||||
import Create from './create/index.vue';
|
||||
import Detail from './detail/index.vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -263,8 +258,6 @@ const paginationConfig = reactive({
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
const acmeAccountRef = ref();
|
||||
const dnsAccountRef = ref();
|
||||
const sslCreateRef = ref();
|
||||
const detailRef = ref();
|
||||
const data = ref();
|
||||
@@ -274,7 +267,6 @@ const sslUploadRef = ref();
|
||||
const applyRef = ref();
|
||||
const logRef = ref();
|
||||
const taskLogRef = ref();
|
||||
const caRef = ref();
|
||||
const obtainRef = ref();
|
||||
const pushFormRef = ref<FormInstance>();
|
||||
const pushOpen = ref(false);
|
||||
@@ -464,10 +456,10 @@ const updateConfig = (row: Website.SSLDTO) => {
|
||||
};
|
||||
|
||||
const openAcmeAccount = () => {
|
||||
acmeAccountRef.value.acceptParams();
|
||||
routerToNameWithQuery('SSL-ACME', { uncached: 'true' });
|
||||
};
|
||||
const openDnsAccount = () => {
|
||||
dnsAccountRef.value.acceptParams();
|
||||
routerToNameWithQuery('SSL-DNS', { uncached: 'true' });
|
||||
};
|
||||
const openSSL = () => {
|
||||
sslCreateRef.value.acceptParams('create');
|
||||
@@ -542,7 +534,7 @@ const submitPush = async () => {
|
||||
};
|
||||
|
||||
const openCA = () => {
|
||||
caRef.value.acceptParams();
|
||||
routerToNameWithQuery('SSL-CA', { uncached: 'true' });
|
||||
};
|
||||
|
||||
const applySSL = (row: Website.SSLDTO) => {
|
||||
|
||||
Reference in New Issue
Block a user