mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-11 01:25:11 +08:00
feat: Support pushing certificates to other nodes (#10074)
Refs https://github.com/1Panel-dev/1Panel/issues/9103
This commit is contained in:
parent
d6f16cf700
commit
d09d686378
45 changed files with 288 additions and 67 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
|
@ -245,3 +246,15 @@ func (b *BaseApi) DownloadWebsiteSSL(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)
|
||||
}
|
||||
|
||||
func (b *BaseApi) ImportMasterSSL(c *gin.Context) {
|
||||
var req model.WebsiteSSL
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteSSLService.ImportMasterSSL(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,26 @@ type WebsiteSSLSearch struct {
|
|||
}
|
||||
|
||||
type WebsiteSSLCreate struct {
|
||||
PrimaryDomain string `json:"primaryDomain" validate:"required"`
|
||||
OtherDomains string `json:"otherDomains"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
AcmeAccountID uint `json:"acmeAccountId" validate:"required"`
|
||||
DnsAccountID uint `json:"dnsAccountId"`
|
||||
AutoRenew bool `json:"autoRenew"`
|
||||
KeyType string `json:"keyType"`
|
||||
Apply bool `json:"apply"`
|
||||
PushDir bool `json:"pushDir"`
|
||||
Dir string `json:"dir"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
DisableCNAME bool `json:"disableCNAME"`
|
||||
SkipDNS bool `json:"skipDNS"`
|
||||
Nameserver1 string `json:"nameserver1"`
|
||||
Nameserver2 string `json:"nameserver2"`
|
||||
ExecShell bool `json:"execShell"`
|
||||
Shell string `json:"shell"`
|
||||
PrimaryDomain string `json:"primaryDomain" validate:"required"`
|
||||
OtherDomains string `json:"otherDomains"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
AcmeAccountID uint `json:"acmeAccountId" validate:"required"`
|
||||
DnsAccountID uint `json:"dnsAccountId"`
|
||||
AutoRenew bool `json:"autoRenew"`
|
||||
KeyType string `json:"keyType"`
|
||||
Apply bool `json:"apply"`
|
||||
PushDir bool `json:"pushDir"`
|
||||
Dir string `json:"dir"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
DisableCNAME bool `json:"disableCNAME"`
|
||||
SkipDNS bool `json:"skipDNS"`
|
||||
Nameserver1 string `json:"nameserver1"`
|
||||
Nameserver2 string `json:"nameserver2"`
|
||||
ExecShell bool `json:"execShell"`
|
||||
Shell string `json:"shell"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes []string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteDNSReq struct {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ type WebsiteSSL struct {
|
|||
DisableCNAME bool `json:"disableCNAME"`
|
||||
ExecShell bool `json:"execShell"`
|
||||
Shell string `json:"shell"`
|
||||
MasterSSLID uint `json:"masterSslId"`
|
||||
Nodes string `json:"nodes"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
|
||||
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
|
||||
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type ISSLRepo interface {
|
|||
WithByDnsAccountId(dnsAccountId uint) DBOption
|
||||
WithByCAID(caID uint) DBOption
|
||||
WithByDomain(domain string) DBOption
|
||||
WithByMasterSSLID(sslID uint) DBOption
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
|
||||
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
|
||||
List(opts ...DBOption) ([]model.WebsiteSSL, error)
|
||||
|
|
@ -52,12 +53,19 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
|
|||
return db.Where("ca_id = ?", caID)
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) WithByDomain(domain string) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("primary_domain Like ? or domains Like ?", "%"+domain+"%", "%"+domain+"%")
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) WithByMasterSSLID(sslID uint) DBOption {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("master_ssl_id = ?", sslID)
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
|
||||
var sslList []model.WebsiteSSL
|
||||
db := getDb(opts...).Model(&model.WebsiteSSL{})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"log"
|
||||
"os"
|
||||
|
|
@ -46,6 +47,7 @@ type IWebsiteSSLService interface {
|
|||
ObtainSSL(apply request.WebsiteSSLApply) error
|
||||
SyncForRestart() error
|
||||
DownloadFile(id uint) (*os.File, error)
|
||||
ImportMasterSSL(create model.WebsiteSSL) error
|
||||
}
|
||||
|
||||
func NewIWebsiteSSLService() IWebsiteSSLService {
|
||||
|
|
@ -146,6 +148,10 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
|
|||
}
|
||||
websiteSSL.Dir = create.Dir
|
||||
}
|
||||
if create.PushNode && global.IsMaster && len(create.Nodes) > 0 {
|
||||
websiteSSL.PushNode = true
|
||||
websiteSSL.Nodes = strings.Join(create.Nodes, ",")
|
||||
}
|
||||
|
||||
var domains []string
|
||||
if create.OtherDomains != "" {
|
||||
|
|
@ -207,7 +213,7 @@ func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{
|
|||
}
|
||||
|
||||
func reloadSystemSSL(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
|
||||
if global.CoreDB == nil {
|
||||
if !global.IsMaster {
|
||||
return
|
||||
}
|
||||
systemSSLEnable, sslID := GetSystemSSL()
|
||||
|
|
@ -387,6 +393,14 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
|
|||
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
|
||||
}
|
||||
reloadSystemSSL(websiteSSL, logger)
|
||||
if websiteSSL.PushNode {
|
||||
printSSLLog(logger, "StartPushSSLToNode", nil, apply.DisableLog)
|
||||
if err = xpack.PushSSLToNode(websiteSSL); err != nil {
|
||||
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()}, apply.DisableLog)
|
||||
return
|
||||
}
|
||||
printSSLLog(logger, "PushSSLToNodeSuccess", nil, apply.DisableLog)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
|
@ -712,3 +726,55 @@ func (w WebsiteSSLService) SyncForRestart() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error {
|
||||
websiteSSL, _ := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID))
|
||||
if websiteSSL == nil {
|
||||
websiteSSL = &model.WebsiteSSL{
|
||||
Status: constant.SSLReady,
|
||||
Provider: constant.FromMaster,
|
||||
PrimaryDomain: create.PrimaryDomain,
|
||||
StartDate: create.StartDate,
|
||||
ExpireDate: create.ExpireDate,
|
||||
KeyType: create.KeyType,
|
||||
Description: create.Description,
|
||||
MasterSSLID: create.ID,
|
||||
PrivateKey: create.PrivateKey,
|
||||
Pem: create.Pem,
|
||||
Type: create.Type,
|
||||
Organization: create.Organization,
|
||||
}
|
||||
if err := websiteSSLRepo.Create(context.TODO(), websiteSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
websiteSSL.PrimaryDomain = create.PrimaryDomain
|
||||
websiteSSL.StartDate = create.StartDate
|
||||
websiteSSL.ExpireDate = create.ExpireDate
|
||||
websiteSSL.KeyType = create.KeyType
|
||||
websiteSSL.Description = create.Description
|
||||
websiteSSL.PrivateKey = create.PrivateKey
|
||||
websiteSSL.Pem = create.Pem
|
||||
websiteSSL.Type = create.Type
|
||||
websiteSSL.Organization = create.Organization
|
||||
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
|
||||
if len(websites) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, website := range websites {
|
||||
if err := createPemFile(website, *websiteSSL); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err == nil {
|
||||
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const (
|
|||
Http = "http"
|
||||
Manual = "manual"
|
||||
SelfSigned = "selfSigned"
|
||||
FromMaster = "fromMaster"
|
||||
|
||||
StartWeb = "start"
|
||||
StopWeb = "stop"
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ StartUpdateSystemSSL: 'Start updating system certificate'
|
|||
UpdateSystemSSLSuccess: 'Update system certificate successfully'
|
||||
ErrWildcardDomain: 'Unable to apply for wildcard domain name certificate in HTTP mode'
|
||||
ErrApplySSLCanNotDelete: "The certificate {{.name}} being applied for cannot be deleted, please try again later."
|
||||
StartPushSSLToNode: "Starting to push certificate to node"
|
||||
PushSSLToNodeFailed: "Failed to push certificate to node: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Successfully pushed certificate to node"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: 'The current user already exists, please re-enter'
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ StartUpdateSystemSSL: 'システム証明書の更新を開始します'
|
|||
UpdateSystemSSLSuccess: 'システム証明書を正常に更新しました'
|
||||
ErrWildcardDomain: 'HTTP モードでワイルドカード ドメイン名証明書を申請できません'
|
||||
ErrApplySSLCanNotDelete: "申請中の証明書 {{.name}} は削除できません。しばらくしてからもう一度お試しください。"
|
||||
StartPushSSLToNode: "証明書をノードにプッシュ開始"
|
||||
PushSSLToNodeFailed: "ノードに証明書をプッシュ失敗: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "ノードに証明書をプッシュ成功"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: '現在のユーザーは既に存在します。再入力してください'
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ StartUpdateSystemSSL: '시스템 인증서 업데이트 시작'
|
|||
UpdateSystemSSLSuccess: '시스템 인증서 업데이트가 성공적으로 완료되었습니다.'
|
||||
ErrWildcardDomain: 'HTTP 모드에서 와일드카드 도메인 이름 인증서를 신청할 수 없습니다'
|
||||
ErrApplySSLCanNotDelete: "신청 중인 인증서 {{.name}}는 삭제할 수 없습니다. 나중에 다시 시도해 주세요."
|
||||
StartPushSSLToNode: "인증서를 노드로 푸시 시작"
|
||||
PushSSLToNodeFailed: "노드로 인증서 푸시 실패: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "노드로 인증서 푸시 성공"
|
||||
|
||||
#마이SQL
|
||||
ErrUserIsExist: '현재 사용자가 이미 존재합니다. 다시 입력하세요'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ ErrApiConfigKeyInvalid: 'Ralat kunci antara muka API: {{ .detail }}'
|
|||
ErrApiConfigIPInvalid: 'IP yang digunakan untuk memanggil antara muka API tiada dalam senarai putih: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'Antara muka ini melarang penggunaan panggilan antara muka API: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'Ralat cap masa antara muka API: {{ .detail }}'
|
||||
StartPushSSLToNode: "Mula menolak sijil ke nod"
|
||||
PushSSLToNodeFailed: "Gagal menolak sijil ke nod: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Berjaya menolak sijil ke nod"
|
||||
|
||||
#biasa
|
||||
ErrUsernameIsExist: 'Nama pengguna sudah wujud'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ ErrApiConfigKeyInvalid: 'Erro de chave da interface da API: {{ .detail }}'
|
|||
ErrApiConfigIPInvalid: 'O IP usado para chamar a interface da API não está na lista de permissões: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'Esta interface proíbe o uso de chamadas de interface de API: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'Erro de registro de data e hora da interface da API: {{ .detail }}'
|
||||
StartPushSSLToNode: "Iniciando o envio do certificado para o nó"
|
||||
PushSSLToNodeFailed: "Falha ao enviar o certificado para o nó: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Certificado enviado com sucesso para o nó"
|
||||
|
||||
#comum
|
||||
ErrUsernameIsExist: 'Nome de usuário já existe'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ ErrApiConfigKeyInvalid: 'Ошибка ключа интерфейса API: {{ .d
|
|||
ErrApiConfigIPInvalid: 'IP-адрес, используемый для вызова интерфейса API, отсутствует в белом списке: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'Этот интерфейс запрещает использование вызовов интерфейса API: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'Ошибка временной метки интерфейса API: {{ .detail }}'
|
||||
StartPushSSLToNode: "Начало отправки сертификата на узел"
|
||||
PushSSLToNodeFailed: "Не удалось отправить сертификат на узел: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Сертификат успешно отправлен на узел"
|
||||
|
||||
#общий
|
||||
ErrUsernameIsExist: 'Имя пользователя уже существует'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ ErrApiConfigKeyInvalid: 'API arayüz anahtarı hatası: {{ .detail }}'
|
|||
ErrApiConfigIPInvalid: 'API arayüzünü çağırmak için kullanılan IP beyaz listede değil: {{ .detail }}'
|
||||
ErrApiConfigDisable: 'Bu arayüz API arayüz çağrılarının kullanımını yasaklıyor: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API arayüz zaman damgası hatası: {{ .detail }}'
|
||||
StartPushSSLToNode: "Sertifika düğüme gönderilmeye başlandı"
|
||||
PushSSLToNodeFailed: "Sertifika düğüme gönderilemedi: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Sertifika düğüme başarıyla gönderildi"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: 'Kullanıcı adı zaten mevcut'
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ StartUpdateSystemSSL: '開始更新系統憑證'
|
|||
UpdateSystemSSLSuccess: '更新系統憑證成功'
|
||||
ErrWildcardDomain: 'HTTP 模式無法申請泛網域憑證'
|
||||
ErrApplySSLCanNotDelete: "正在申請的證書 {{.name}} 無法刪除,請稍後再試"
|
||||
StartPushSSLToNode: "開始推送證書到節點"
|
||||
PushSSLToNodeFailed: "推送證書到節點失敗: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "推送證書到節點成功"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: '目前使用者已存在,請重新輸入'
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ StartUpdateSystemSSL: "开始更新系统证书"
|
|||
UpdateSystemSSLSuccess: "更新系统证书成功"
|
||||
ErrWildcardDomain: "HTTP 模式无法申请泛域名证书"
|
||||
ErrApplySSLCanNotDelete: "正在申请的证书{{.name}}无法删除,请稍后再试"
|
||||
StartPushSSLToNode: "开始推送证书到节点"
|
||||
PushSSLToNodeFailed: "推送证书到节点失败: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "推送证书到节点成功"
|
||||
|
||||
#mysql
|
||||
ErrUserIsExist: "当前用户已存在,请重新输入"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ func InitAgentDB() {
|
|||
migrations.UpdateMcpServer,
|
||||
migrations.InitCronjobGroup,
|
||||
migrations.AddColumnToAlert,
|
||||
migrations.UpdateWebsiteSSL,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -461,3 +461,13 @@ var AddColumnToAlert = &gormigrate.Migration{
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateWebsiteSSL = &gormigrate.Migration{
|
||||
ID: "20250819-update-website-ssl",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ func (a *WebsiteSSLRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
groupRouter.POST("/upload", baseApi.UploadWebsiteSSL)
|
||||
groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL)
|
||||
groupRouter.POST("/download", baseApi.DownloadWebsiteSSL)
|
||||
groupRouter.POST("/import", baseApi.ImportMasterSSL)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,8 @@ func getCaDirURL(accountType, customCaURL string) string {
|
|||
var caDirURL string
|
||||
switch accountType {
|
||||
case "letsencrypt":
|
||||
caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
//caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
caDirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
case "zerossl":
|
||||
caDirURL = "https://acme.zerossl.com/v2/DV90"
|
||||
case "buypass":
|
||||
|
|
|
|||
|
|
@ -85,3 +85,7 @@ func LoadRequestTransport() *http.Transport {
|
|||
func ValidateCertificate(c *gin.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,4 +227,7 @@ ErrMasterDelete: "Unable to delete the master node, please delete the slave node
|
|||
ClusterNameIsExist: "Cluster name already exists."
|
||||
AppStatusUnHealthy: "Application status acquisition is abnormal, please check the installation node status in the node list."
|
||||
MasterNodePortNotAvailable: "Node {{ .name }} port {{ .port }} connectivity verification failed, please check firewall/security group settings and master node status."
|
||||
ClusterMasterNotExist: "The master node of the cluster is disconnected, please delete the child nodes."
|
||||
ClusterMasterNotExist: "The master node of the cluster is disconnected, please delete the child nodes."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} request failed: {{ .err }}"
|
||||
|
|
@ -228,4 +228,7 @@ ErrMasterDelete: "マスターノードを削除できません。スレーブ
|
|||
ClusterNameIsExist: "クラスタ名は既に存在します。"
|
||||
AppStatusUnHealthy: "アプリケーションのステータス取得が異常です。ノードリストでインストールノードのステータスを確認してください。"
|
||||
MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の接続性検証が失敗しました。ファイアウォール/セキュリティグループの設定とマスターノードのステータスを確認してください。"
|
||||
ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。"
|
||||
ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。"
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}"
|
||||
|
|
@ -227,4 +227,7 @@ ErrMasterDelete: "마스터 노드를 삭제할 수 없습니다. 슬레이브
|
|||
ClusterNameIsExist: "클러스터 이름이 이미 존재합니다."
|
||||
AppStatusUnHealthy: "애플리케이션 상태 획득이 비정상입니다. 노드 목록에서 설치 노드 상태를 확인하세요."
|
||||
MasterNodePortNotAvailable: "노드 {{ .name }} 포트 {{ .port }} 연결성 검증에 실패했습니다. 방화벽/보안 그룹 설정 및 마스터 노드 상태를 확인하세요."
|
||||
ClusterMasterNotExist: "클러스터의 마스터 노드가 연결이 끊어졌습니다. 자식 노드를 삭제하세요."
|
||||
ClusterMasterNotExist: "클러스터의 마스터 노드가 연결이 끊어졌습니다. 자식 노드를 삭제하세요."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}"
|
||||
|
|
@ -222,4 +222,7 @@ ErrMasterDelete: "Tidak dapat menghapus nod utama, sila hapuskan nod perantara d
|
|||
ClusterNameIsExist: "Nama kluster sudah wujud."
|
||||
AppStatusUnHealthy: "Pengambilan status aplikasi tidak normal, sila periksa status nod pemasangan dalam senarai nod."
|
||||
MasterNodePortNotAvailable: "Pengesahan kesambungan pelabuhan {{ .name }} nod {{ .port }} gagal, sila periksa tetapan firewall/kumpulan keselamatan dan status nod utama."
|
||||
ClusterMasterNotExist: "Node utama kluster terputus, sila padamkan nod anak."
|
||||
ClusterMasterNotExist: "Node utama kluster terputus, sila padamkan nod anak."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} permintaan gagal: {{ .err }}"
|
||||
|
|
@ -227,4 +227,7 @@ ErrMasterDelete: "Não é possível excluir o nó mestre, exclua os nós escravo
|
|||
ClusterNameIsExist: "O nome do cluster já existe."
|
||||
AppStatusUnHealthy: "A aquisição do status do aplicativo está anormal, verifique o status dos nós de instalação na lista de nós."
|
||||
MasterNodePortNotAvailable: "A verificação de conectividade da porta {{ .port }} do nó {{ .name }} falhou, verifique as configurações de firewall/grupo de segurança e o status do nó mestre."
|
||||
ClusterMasterNotExist: "O nó mestre do cluster está desconectado, por favor, exclua os nós filhos."
|
||||
ClusterMasterNotExist: "O nó mestre do cluster está desconectado, por favor, exclua os nós filhos."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} solicitação falhou: {{ .err }}
|
||||
|
|
@ -227,4 +227,7 @@ ErrMasterDelete: "Невозможно удалить основной узел,
|
|||
ClusterNameIsExist: "Имя кластера уже существует."
|
||||
AppStatusUnHealthy: "Получение статуса приложения аномально, пожалуйста, проверьте статус узлов установки в списке узлов."
|
||||
MasterNodePortNotAvailable: "Проверка подключения порта {{ .port }} узла {{ .name }} не удалась, пожалуйста, проверьте настройки брандмауэра/группы безопасности и статус главного узла."
|
||||
ClusterMasterNotExist: "Основной узел кластера отключен, пожалуйста, удалите дочерние узлы."
|
||||
ClusterMasterNotExist: "Основной узел кластера отключен, пожалуйста, удалите дочерние узлы."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}"
|
||||
|
|
@ -226,4 +226,7 @@ ErrMasterDelete: "Ana düğümü silinemiyor, lütfen önce alt düğümleri sil
|
|||
ClusterNameIsExist: "Küme adı zaten var."
|
||||
AppStatusUnHealthy: "Uygulama durumu alımı anormal, lütfen düğüm listesindeki yükleme düğümü durumunu kontrol edin."
|
||||
MasterNodePortNotAvailable: "Düğüm {{ .name }} portu {{ .port }} bağlantı doğrulaması başarısız oldu, lütfen güvenlik duvarı/güvenlik grubu ayarlarını ve ana düğüm durumunu kontrol edin."
|
||||
ClusterMasterNotExist: "Küme ana düğümü bağlantısı kesildi, lütfen alt düğümleri silin."
|
||||
ClusterMasterNotExist: "Küme ana düğümü bağlantısı kesildi, lütfen alt düğümleri silin."
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} istek başarısız: {{ .err }}"
|
||||
|
|
@ -236,4 +236,7 @@ ErrMasterDelete: "無法刪除主節點,請先刪除從節點。"
|
|||
ClusterNameIsExist: "集群名稱已存在。"
|
||||
AppStatusUnHealthy: "應用獲取狀態異常,請在節點列表檢查安裝節點狀態。"
|
||||
MasterNodePortNotAvailable: "節點 {{ .name }} 端口 {{ .port }} 連通性校驗失敗,請檢查防火牆/安全組設置和主節點狀態。"
|
||||
ClusterMasterNotExist: "集群主節點失聯,請刪除子節點。"
|
||||
ClusterMasterNotExist: "集群主節點失聯,請刪除子節點。"
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} 請求失敗: {{ .err }}"
|
||||
|
|
@ -236,4 +236,7 @@ ErrMasterDelete: "无法删除主节点,请先删除从节点"
|
|||
ClusterNameIsExist: "集群名称已存在"
|
||||
AppStatusUnHealthy: "应用获取状态异常,请在节点列表检查安装节点状态"
|
||||
MasterNodePortNotAvailable: "节点 {{ .name }} 端口 {{ .port }} 连通性校验失败,请检查防火墙/安全组设置和主节点状态"
|
||||
ClusterMasterNotExist: "集群主节点失联,请删除子节点"
|
||||
ClusterMasterNotExist: "集群主节点失联,请删除子节点"
|
||||
|
||||
#ssl
|
||||
ErrReqFailed: "{{.name}} 请求失败: {{ .err }}"
|
||||
|
|
@ -44,7 +44,7 @@ func Proxy() gin.HandlerFunc {
|
|||
|
||||
apiReq := c.GetBool("API_AUTH")
|
||||
|
||||
if !apiReq && strings.HasPrefix(c.Request.URL.Path, "/api/v2/") && !checkSession(c) {
|
||||
if !apiReq && strings.HasPrefix(c.Request.URL.Path, "/api/v2/") && !isLocalAPI(c.Request.URL.Path) && !checkSession(c) {
|
||||
data, _ := res.ErrorMsg.ReadFile("html/401.html")
|
||||
c.Data(401, "text/html; charset=utf-8", data)
|
||||
c.Abort()
|
||||
|
|
@ -89,3 +89,7 @@ func checkSession(c *gin.Context) bool {
|
|||
_ = global.SESSION.Set(c, psession, httpsSetting.Value == constant.StatusEnable, lifeTime)
|
||||
return true
|
||||
}
|
||||
|
||||
func isLocalAPI(urlPath string) bool {
|
||||
return urlPath == "/api/v2/core/xpack/sync/ssl"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,11 @@ const handleClose = () => {
|
|||
};
|
||||
|
||||
const beforeClose = (done: () => void) => {
|
||||
emit('beforeClose', done);
|
||||
if (!props.confirmBeforeClose) {
|
||||
done();
|
||||
} else {
|
||||
emit('beforeClose', done);
|
||||
}
|
||||
};
|
||||
|
||||
function toggleFullscreen() {
|
||||
|
|
|
|||
|
|
@ -2669,6 +2669,9 @@ const message = {
|
|||
customAcme: 'Custom ACME Service',
|
||||
customAcmeURL: 'ACME Service URL',
|
||||
baiduCloud: 'Baidu Cloud',
|
||||
pushNode: 'Sync to Other Nodes',
|
||||
pushNodeHelper: 'Push to selected nodes after application/renewal',
|
||||
fromMaster: 'Master Node Push',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Create rule',
|
||||
|
|
|
|||
|
|
@ -2582,6 +2582,9 @@ const message = {
|
|||
customAcme: 'カスタム ACME サービス',
|
||||
customAcmeURL: 'ACME サービス URL',
|
||||
baiduCloud: '百度クラウド',
|
||||
pushNode: '他のノードに同期',
|
||||
pushNodeHelper: '申請/更新後に選択したノードにプッシュ',
|
||||
fromMaster: 'マスターノードからのプッシュ',
|
||||
},
|
||||
firewall: {
|
||||
create: 'ルールを作成します',
|
||||
|
|
|
|||
|
|
@ -2535,6 +2535,9 @@ const message = {
|
|||
customAcme: '사용자 정의 ACME 서비스',
|
||||
customAcmeURL: 'ACME 서비스 URL',
|
||||
baiduCloud: '바이두 클라우드',
|
||||
pushNode: '다른 노드에 동기화',
|
||||
pushNodeHelper: '신청/갱신 후 선택한 노드로 푸시',
|
||||
fromMaster: '마스터 노드에서 푸시',
|
||||
},
|
||||
firewall: {
|
||||
create: '규칙 만들기',
|
||||
|
|
|
|||
|
|
@ -2642,6 +2642,9 @@ const message = {
|
|||
customAcme: 'Perkhidmatan ACME Tersuai',
|
||||
customAcmeURL: 'URL Perkhidmatan ACME',
|
||||
baiduCloud: 'Baidu Cloud',
|
||||
pushNode: 'Segerakan ke Nod Lain',
|
||||
pushNodeHelper: 'Tolak ke nod terpilih selepas permohonan/pembaharuan',
|
||||
fromMaster: 'Tolak dari Nod Utama',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Buat peraturan',
|
||||
|
|
|
|||
|
|
@ -2643,6 +2643,9 @@ const message = {
|
|||
customAcme: 'Serviço ACME Personalizado',
|
||||
customAcmeURL: 'URL do Serviço ACME',
|
||||
baiduCloud: 'Baidu Cloud',
|
||||
pushNode: 'Sincronizar com Outros Nós',
|
||||
pushNodeHelper: 'Enviar para os nós selecionados após a aplicação/renovação',
|
||||
fromMaster: 'Envio do Nó Mestre',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Criar regra',
|
||||
|
|
|
|||
|
|
@ -2638,6 +2638,9 @@ const message = {
|
|||
customAcme: 'Пользовательская служба ACME',
|
||||
customAcmeURL: 'URL службы ACME',
|
||||
baiduCloud: 'Baidu Cloud',
|
||||
pushNode: 'Синхронизация с другими узлами',
|
||||
pushNodeHelper: 'Отправить на выбранные узлы после заявки/продления',
|
||||
fromMaster: 'Отправка с главного узла',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Создать правило',
|
||||
|
|
|
|||
|
|
@ -2702,6 +2702,9 @@ const message = {
|
|||
customAcme: 'Özel ACME Servisi',
|
||||
customAcmeURL: 'ACME Servis URL’si',
|
||||
baiduCloud: 'Baidu Cloud',
|
||||
pushNode: 'Diğer Düğümlere Senkronize Et',
|
||||
pushNodeHelper: 'Başvuru/yenilemeden sonra seçilen düğümlere gönder',
|
||||
fromMaster: 'Ana Düğümden Gönder',
|
||||
},
|
||||
firewall: {
|
||||
create: 'Kural oluştur',
|
||||
|
|
|
|||
|
|
@ -2489,6 +2489,9 @@ const message = {
|
|||
customAcme: '自訂 ACME 服務',
|
||||
customAcmeURL: 'ACME 服務 URL',
|
||||
baiduCloud: '百度雲',
|
||||
pushNode: '同步到其他節點',
|
||||
pushNodeHelper: '申請/續期之後推送到選擇的節點',
|
||||
fromMaster: '主節點推送',
|
||||
},
|
||||
firewall: {
|
||||
create: '創建規則',
|
||||
|
|
|
|||
|
|
@ -2479,6 +2479,9 @@ const message = {
|
|||
customAcme: '自定义 ACME 服务',
|
||||
customAcmeURL: 'ACME 服务 URL',
|
||||
baiduCloud: '百度云',
|
||||
pushNode: '同步到其他节点',
|
||||
pushNodeHelper: '申请/续期之后推送到选择的节点',
|
||||
fromMaster: '主节点推送',
|
||||
},
|
||||
firewall: {
|
||||
create: '创建规则',
|
||||
|
|
|
|||
|
|
@ -440,6 +440,8 @@ export function getProvider(provider: string): string {
|
|||
return 'HTTP';
|
||||
case 'selfSigned':
|
||||
return i18n.global.t('ssl.selfSigned');
|
||||
case 'fromMaster':
|
||||
return i18n.global.t('ssl.fromMaster');
|
||||
default:
|
||||
return i18n.global.t('ssl.manualCreate');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { deleteOllamaModel } from '@/api/modules/ai';
|
|||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'OpDialog' });
|
||||
|
||||
|
|
@ -97,8 +97,6 @@ const handleClose = () => {
|
|||
open.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -258,14 +258,6 @@ onMounted(async () => {
|
|||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
// .tag-button {
|
||||
// margin-right: 10px;
|
||||
// &.no-active {
|
||||
// background: none;
|
||||
// border: none;
|
||||
// }
|
||||
// }
|
||||
|
||||
@media only screen and (min-width: 768px) and (max-width: 1200px) {
|
||||
.app-col-12 {
|
||||
max-width: 50%;
|
||||
|
|
|
|||
|
|
@ -94,28 +94,6 @@
|
|||
<el-form-item :label="''" prop="autoRenew" v-if="ssl.provider !== 'dnsManual'">
|
||||
<el-checkbox v-model="ssl.autoRenew" :label="$t('ssl.autoRenew')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="pushDir">
|
||||
<el-checkbox v-model="ssl.pushDir" :label="$t('ssl.pushDir')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.dir')" prop="dir" v-if="ssl.pushDir">
|
||||
<el-input v-model.trim="ssl.dir">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ path: ssl.dir, dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.pushDirHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="execShell">
|
||||
<el-checkbox v-model="ssl.execShell" :label="$t('ssl.execShell')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.shell')" prop="shell" v-if="ssl.execShell">
|
||||
<el-input type="textarea" :rows="4" v-model="ssl.shell" />
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.shellHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div v-if="ssl.provider != 'selfSigned'">
|
||||
<el-form-item :label="''" prop="disableCNAME">
|
||||
<el-checkbox v-model="ssl.disableCNAME" :label="$t('ssl.disableCNAME')" />
|
||||
|
|
@ -141,6 +119,35 @@
|
|||
{{ $t('ssl.nameserverHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="pushDir">
|
||||
<el-checkbox v-model="ssl.pushDir" :label="$t('ssl.pushDir')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.dir')" prop="dir" v-if="ssl.pushDir">
|
||||
<el-input v-model.trim="ssl.dir">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="fileRef.acceptParams({ path: ssl.dir, dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.pushDirHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="''" prop="execShell">
|
||||
<el-checkbox v-model="ssl.execShell" :label="$t('ssl.execShell')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ssl.shell')" prop="shell" v-if="ssl.execShell">
|
||||
<el-input type="textarea" :rows="4" v-model="ssl.shell" />
|
||||
<span class="input-help">
|
||||
{{ $t('ssl.shellHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<PushtoNode
|
||||
v-if="isMaster && isMasterProductPro"
|
||||
:push-node="ssl.pushNode"
|
||||
:nodes="ssl.nodes"
|
||||
@update:push-node="ssl.pushNode = $event"
|
||||
@update:nodes="ssl.nodes = $event"
|
||||
/>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
|
@ -166,6 +173,18 @@ import { computed, reactive, ref } from 'vue';
|
|||
import { MsgSuccess } from '@/utils/message';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
import { getDNSName, getAccountName } from '@/utils/util';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isMasterProductPro, isMaster } = 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 props = defineProps({
|
||||
id: {
|
||||
|
|
@ -205,6 +224,7 @@ const rules = ref({
|
|||
nameserver2: [Rules.ipv4],
|
||||
shell: [Rules.requiredInput],
|
||||
description: [checkMaxLength(128)],
|
||||
nodes: [Rules.requiredSelect],
|
||||
});
|
||||
const websiteID = ref();
|
||||
|
||||
|
|
@ -227,6 +247,8 @@ const initData = () => ({
|
|||
nameserver2: '',
|
||||
execShell: false,
|
||||
shell: '',
|
||||
pushNode: false,
|
||||
nodes: [],
|
||||
});
|
||||
|
||||
const ssl = ref(initData());
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
prop="domains"
|
||||
min-width="90px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('ssl.applyType')" show-overflow-tooltip prop="provider" width="90px">
|
||||
<el-table-column :label="$t('ssl.applyType')" show-overflow-tooltip prop="provider" width="120px">
|
||||
<template #default="{ row }">{{ getProvider(row.provider) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
|
@ -97,7 +97,12 @@
|
|||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.button.log')" width="80px">
|
||||
<template #default="{ row }">
|
||||
<el-button @click="openSSLLog(row)" link type="primary" v-if="row.provider != 'manual'">
|
||||
<el-button
|
||||
@click="openSSLLog(row)"
|
||||
link
|
||||
type="primary"
|
||||
v-if="row.provider != 'manual' && row.provider !== 'fromMaster'"
|
||||
>
|
||||
{{ $t('website.check') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
|
@ -224,7 +229,7 @@ const buttons = [
|
|||
{
|
||||
label: i18n.global.t('ssl.apply'),
|
||||
disabled: function (row: Website.SSLDTO) {
|
||||
return row.status === 'applying' || row.provider === 'manual';
|
||||
return row.status === 'applying' || row.provider === 'manual' || row.provider === 'fromMaster';
|
||||
},
|
||||
click: function (row: Website.SSLDTO) {
|
||||
if (row.provider === 'dnsManual') {
|
||||
|
|
@ -248,6 +253,9 @@ const buttons = [
|
|||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
disabled: function (row: Website.SSLDTO) {
|
||||
return row.provider === 'fromMaster';
|
||||
},
|
||||
click: function (row: Website.SSLDTO) {
|
||||
onEdit(row);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue