1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-15 02:04:46 +08:00

feat: 优化网站域名添加 (#6069)

This commit is contained in:
zhengkunwang 2024-08-08 18:26:36 +08:00 committed by GitHub
parent 94b5a7fae4
commit a225d2d79a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 313 additions and 185 deletions

View File

@ -13,15 +13,15 @@ type WebsiteSearch struct {
} }
type WebsiteCreate struct { type WebsiteCreate struct {
PrimaryDomain string `json:"primaryDomain" validate:"required"`
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`
Alias string `json:"alias" validate:"required"` Alias string `json:"alias" validate:"required"`
Remark string `json:"remark"` Remark string `json:"remark"`
OtherDomains string `json:"otherDomains"`
Proxy string `json:"proxy"` Proxy string `json:"proxy"`
WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"` WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"`
IPV6 bool `json:"IPV6"` IPV6 bool `json:"IPV6"`
Domains []WebsiteDomain `json:"domains"`
AppType string `json:"appType" validate:"oneof=new installed"` AppType string `json:"appType" validate:"oneof=new installed"`
AppInstall NewAppInstall `json:"appInstall"` AppInstall NewAppInstall `json:"appInstall"`
AppID uint `json:"appID"` AppID uint `json:"appID"`
@ -124,7 +124,13 @@ type WebsiteGroupUpdate struct {
type WebsiteDomainCreate struct { type WebsiteDomainCreate struct {
WebsiteID uint `json:"websiteID" validate:"required"` WebsiteID uint `json:"websiteID" validate:"required"`
Domains string `json:"domains" validate:"required"` Domains []WebsiteDomain `json:"domains" validate:"required"`
}
type WebsiteDomain struct {
Domain string `json:"domain" validate:"required"`
Port int `json:"port"`
SSL bool `json:"SSL"`
} }
type WebsiteDomainDelete struct { type WebsiteDomainDelete struct {
@ -145,7 +151,7 @@ type WebsiteHTTPSOp struct {
SSLProtocol []string `json:"SSLProtocol"` SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"` Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"` Hsts bool `json:"hsts"`
HttpsPort int `json:"httpsPort"` HttpsPorts []int `json:"httpsPorts"`
} }
type WebsiteNginxUpdate struct { type WebsiteNginxUpdate struct {

View File

@ -59,7 +59,8 @@ type WebsiteHTTPS struct {
SSLProtocol []string `json:"SSLProtocol"` SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"` Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"` Hsts bool `json:"hsts"`
HttpsPort int `json:"httpsPort"` HttpsPorts []int `json:"httpsPorts"`
HttpsPort string `json:"httpsPort"`
} }
type WebsiteLog struct { type WebsiteLog struct {

View File

@ -28,8 +28,6 @@ type Website struct {
AppInstallID uint `gorm:"type:integer" json:"appInstallId"` AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
FtpID uint `gorm:"type:integer" json:"ftpId"` FtpID uint `gorm:"type:integer" json:"ftpId"`
HttpsPort int `json:"httpsPort"`
User string `gorm:"type:varchar;" json:"user"` User string `gorm:"type:varchar;" json:"user"`
Group string `gorm:"type:varchar;" json:"group"` Group string `gorm:"type:varchar;" json:"group"`

View File

@ -4,6 +4,7 @@ type WebsiteDomain struct {
BaseModel BaseModel
WebsiteID uint `gorm:"column:website_id;type:varchar(64);not null;" json:"websiteId"` WebsiteID uint `gorm:"column:website_id;type:varchar(64);not null;" json:"websiteId"`
Domain string `gorm:"type:varchar(256);not null" json:"domain"` Domain string `gorm:"type:varchar(256);not null" json:"domain"`
SSL bool `json:"SSL"`
Port int `gorm:"type:integer" json:"port"` Port int `gorm:"type:integer" json:"port"`
} }

View File

@ -225,28 +225,21 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
return err return err
} }
defaultHttpPort := nginxInstall.HttpPort defaultHttpPort := nginxInstall.HttpPort
defaultHttpsPort := nginxInstall.HttpsPort
var ( var (
otherDomains []model.WebsiteDomain
domains []model.WebsiteDomain domains []model.WebsiteDomain
) )
domains, _, _, err = getWebsiteDomains(create.PrimaryDomain, defaultHttpPort, 0) domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, 0)
if err != nil { if err != nil {
return err return err
} }
otherDomains, _, _, err = getWebsiteDomains(create.OtherDomains, defaultHttpPort, 0) primaryDomain := domains[0].Domain
if err != nil { if domains[0].Port != defaultHttpPort {
return err primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port)
}
domains = append(domains, otherDomains...)
if len(domains) == 1 && domains[0].Port != defaultHttpPort {
defaultHttpsPort = domains[0].Port
} }
defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate) defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
website := &model.Website{ website := &model.Website{
PrimaryDomain: create.PrimaryDomain, PrimaryDomain: primaryDomain,
Type: create.Type, Type: create.Type,
Alias: alias, Alias: alias,
Remark: create.Remark, Remark: create.Remark,
@ -259,7 +252,6 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
AccessLog: true, AccessLog: true,
ErrorLog: true, ErrorLog: true,
IPV6: create.IPV6, IPV6: create.IPV6,
HttpsPort: defaultHttpsPort,
} }
var ( var (
@ -267,7 +259,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
runtime *model.Runtime runtime *model.Runtime
) )
createTask, err := task.NewTaskWithOps(create.PrimaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0) createTask, err := task.NewTaskWithOps(primaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0)
if err != nil { if err != nil {
return err return err
} }
@ -464,7 +456,6 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
SSLProtocol: []string{"TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"}, SSLProtocol: []string{"TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"},
Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED", Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED",
Hsts: true, Hsts: true,
HttpsPort: website.HttpsPort,
} }
if err = applySSL(website, *websiteModel, appSSLReq); err != nil { if err = applySSL(website, *websiteModel, appSSLReq); err != nil {
return err return err
@ -607,7 +598,6 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
var ( var (
domainModels []model.WebsiteDomain domainModels []model.WebsiteDomain
addPorts []int addPorts []int
addDomains []string
) )
httpPort, _, err := getAppInstallPort(constant.AppOpenresty) httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
if err != nil { if err != nil {
@ -618,7 +608,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
return nil, err return nil, err
} }
domainModels, addPorts, addDomains, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID) domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -626,7 +616,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
_ = OperateFirewallPort(nil, addPorts) _ = OperateFirewallPort(nil, addPorts)
}() }()
if err := addListenAndServerName(website, addPorts, addDomains); err != nil { if err = addListenAndServerName(website, domainModels); err != nil {
return nil, err return nil, err
} }
@ -868,8 +858,22 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
if err != nil { if err != nil {
return response.WebsiteHTTPS{}, err return response.WebsiteHTTPS{}, err
} }
var res response.WebsiteHTTPS var (
res.HttpsPort = website.HttpsPort res response.WebsiteHTTPS
httpsPorts []string
)
websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId))
for _, domain := range websiteDomains {
if domain.SSL {
httpsPorts = append(httpsPorts, strconv.Itoa(domain.Port))
}
}
if len(httpsPorts) == 0 {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
res.HttpsPort = strconv.Itoa(nginxInstall.HttpsPort)
} else {
res.HttpsPort = strings.Join(httpsPorts, ",")
}
if website.WebsiteSSLID == 0 { if website.WebsiteSSLID == 0 {
res.Enable = false res.Enable = false
return res, nil return res, nil
@ -925,17 +929,17 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
if !req.Enable { if !req.Enable {
website.Protocol = constant.ProtocolHTTP website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0 website.WebsiteSSLID = 0
httpsPort := website.HttpsPort //httpsPort := req.HttpsPort
if httpsPort == 0 { //if len(httpsPort) == 0 {
_, httpsPort, err = getAppInstallPort(constant.AppOpenresty) // _, httpsPort, err = getAppInstallPort(constant.AppOpenresty)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
} //}
httpsPortStr := strconv.Itoa(httpsPort) //httpsPortStr := strconv.Itoa(httpsPort)
if err := deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil { //if err = deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil {
return nil, err // return nil, err
} //}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil) nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
nginxParams = append(nginxParams, nginxParams = append(nginxParams,
dto.NginxParam{ dto.NginxParam{
@ -1035,18 +1039,18 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
} }
website.Protocol = constant.ProtocolHTTPS website.Protocol = constant.ProtocolHTTPS
if err := applySSL(&website, websiteSSL, req); err != nil { if err = applySSL(&website, websiteSSL, req); err != nil {
return nil, err return nil, err
} }
website.HttpConfig = req.HttpConfig website.HttpConfig = req.HttpConfig
if websiteSSL.ID == 0 { if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { if err = websiteSSLRepo.Create(ctx, &websiteSSL); err != nil {
return nil, err return nil, err
} }
website.WebsiteSSLID = websiteSSL.ID website.WebsiteSSLID = websiteSSL.ID
} }
if err := websiteRepo.Save(ctx, &website); err != nil { if err = websiteRepo.Save(ctx, &website); err != nil {
return nil, err return nil, err
} }
return &res, nil return &res, nil

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -35,40 +34,6 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
func getDomain(domainStr string, defaultPort int) (model.WebsiteDomain, error) {
var (
err error
domain = model.WebsiteDomain{}
portN int
)
domainArray := strings.Split(domainStr, ":")
if len(domainArray) == 1 {
domain.Domain, err = handleChineseDomain(domainArray[0])
if err != nil {
return domain, err
}
domain.Port = defaultPort
return domain, nil
}
if len(domainArray) > 1 {
domain.Domain, err = handleChineseDomain(domainArray[0])
if err != nil {
return domain, err
}
portStr := domainArray[1]
portN, err = strconv.Atoi(portStr)
if err != nil {
return domain, buserr.WithName("ErrTypePort", portStr)
}
if portN <= 0 || portN > 65535 {
return domain, buserr.New("ErrTypePortRange")
}
domain.Port = portN
return domain, nil
}
return domain, nil
}
func handleChineseDomain(domain string) (string, error) { func handleChineseDomain(domain string) (string, error) {
if common.ContainsChinese(domain) { if common.ContainsChinese(domain) {
return common.PunycodeEncode(domain) return common.PunycodeEncode(domain)
@ -481,7 +446,7 @@ func delWafConfig(website model.Website, force bool) error {
return nil return nil
} }
func addListenAndServerName(website model.Website, ports []int, domains []string) error { func addListenAndServerName(website model.Website, domains []model.WebsiteDomain) error {
nginxFull, err := getNginxFull(&website) nginxFull, err := getNginxFull(&website)
if err != nil { if err != nil {
return nil return nil
@ -489,16 +454,20 @@ func addListenAndServerName(website model.Website, ports []int, domains []string
nginxConfig := nginxFull.SiteConfig nginxConfig := nginxFull.SiteConfig
config := nginxFull.SiteConfig.Config config := nginxFull.SiteConfig.Config
server := config.FindServers()[0] server := config.FindServers()[0]
for _, port := range ports {
server.AddListen(strconv.Itoa(port), false)
if website.IPV6 {
server.UpdateListen("[::]:"+strconv.Itoa(port), false)
}
}
for _, domain := range domains { for _, domain := range domains {
server.AddServerName(domain) var params []string
if website.Protocol == constant.ProtocolHTTPS && domain.SSL {
params = append(params, "ssl", "http2")
} }
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { server.AddListen(strconv.Itoa(domain.Port), false, params...)
if website.IPV6 {
server.UpdateListen("[::]:"+strconv.Itoa(domain.Port), false, params...)
}
server.UpdateServerName([]string{domain.Domain})
}
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err return err
} }
return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName) return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName)
@ -568,6 +537,24 @@ func createPemFile(website model.Website, websiteSSL model.WebsiteSSL) error {
return nil return nil
} }
func getHttpsPort(website *model.Website) ([]int, error) {
websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID))
var httpsPorts []int
for _, domain := range websiteDomains {
if domain.SSL {
httpsPorts = append(httpsPorts, domain.Port)
}
}
if len(httpsPorts) == 0 {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
}
httpsPorts = append(httpsPorts, nginxInstall.HttpsPort)
}
return httpsPorts, nil
}
func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.WebsiteHTTPSOp) error { func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.WebsiteHTTPSOp) error {
nginxFull, err := getNginxFull(website) nginxFull, err := getNginxFull(website)
if err != nil { if err != nil {
@ -587,18 +574,19 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
server := config.FindServers()[0] server := config.FindServers()[0]
httpPort := strconv.Itoa(nginxFull.Install.HttpPort) httpPort := strconv.Itoa(nginxFull.Install.HttpPort)
httpsPort := nginxFull.Install.HttpsPort httpsPort, err := getHttpsPort(website)
if req.HttpsPort > 0 { if err != nil {
httpsPort = req.HttpsPort return err
} }
website.HttpsPort = httpsPort
httpPortIPV6 := "[::]:" + httpPort httpPortIPV6 := "[::]:" + httpPort
httpsPortIPV6 := "[::]:" + strconv.Itoa(httpsPort)
server.UpdateListen(strconv.Itoa(httpsPort), website.DefaultServer, "ssl", "http2") for _, port := range httpsPort {
httpsPortIPV6 := "[::]:" + strconv.Itoa(port)
server.UpdateListen(strconv.Itoa(port), website.DefaultServer, "ssl", "http2")
if website.IPV6 { if website.IPV6 {
server.UpdateListen(httpsPortIPV6, website.DefaultServer, "ssl", "http2") server.UpdateListen(httpsPortIPV6, website.DefaultServer, "ssl", "http2")
} }
}
switch req.HttpConfig { switch req.HttpConfig {
case constant.HTTPSOnly: case constant.HTTPSOnly:
@ -911,30 +899,36 @@ func changeServiceName(newComposeContent, newServiceName string) (composeByte []
return yaml.Marshal(composeMap) return yaml.Marshal(composeMap)
} }
func getWebsiteDomains(domains string, defaultPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) { func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) {
var ( var (
ports = make(map[int]struct{}) ports = make(map[int]struct{})
existPort = make(map[int]struct{})
) )
domainArray := strings.Split(domains, "\n") existDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID))
for _, domain := range domainArray { for _, domain := range existDomains {
if domain == "" { existPort[domain.Port] = struct{}{}
}
for _, domain := range domains {
if domain.Domain == "" {
continue continue
} }
if !common.IsValidDomain(domain) { if !common.IsValidDomain(domain.Domain) {
err = buserr.WithName("ErrDomainFormat", domain) err = buserr.WithName("ErrDomainFormat", domain.Domain)
return return
} }
var domainModel model.WebsiteDomain var domainModel model.WebsiteDomain
domainModel, err = getDomain(domain, defaultPort) domainModel.Domain, err = handleChineseDomain(domain.Domain)
if err != nil { if err != nil {
return return
} }
if reflect.DeepEqual(domainModel, model.WebsiteDomain{}) { domainModel.Port = domain.Port
continue if domain.Port == 0 {
domain.Port = defaultPort
} }
domainModel.SSL = domain.SSL
domainModel.WebsiteID = websiteID domainModel.WebsiteID = websiteID
domainModels = append(domainModels, domainModel) domainModels = append(domainModels, domainModel)
if domainModel.Port != defaultPort { if _, ok := existPort[domainModel.Port]; !ok {
ports[domainModel.Port] = struct{}{} ports[domainModel.Port] = struct{}{}
} }
if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domainModel.Domain), websiteDomainRepo.WithWebsiteId(websiteID)); exist.ID == 0 { if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domainModel.Domain), websiteDomainRepo.WithWebsiteId(websiteID)); exist.ID == 0 {
@ -950,6 +944,10 @@ func getWebsiteDomains(domains string, defaultPort int, websiteID uint) (domainM
} }
for port := range ports { for port := range ports {
if port == defaultPort {
addPorts = append(addPorts, port)
continue
}
if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 { if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 {
errMap := make(map[string]interface{}) errMap := make(map[string]interface{})
errMap["port"] = port errMap["port"] = port

View File

@ -19,6 +19,7 @@ func Init() {
migrations.InitPHPExtensions, migrations.InitPHPExtensions,
migrations.AddTask, migrations.AddTask,
migrations.UpdateWebsite, migrations.UpdateWebsite,
migrations.UpdateWebsiteDomain,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -296,3 +296,11 @@ var UpdateWebsite = &gormigrate.Migration{
&model.Website{}) &model.Website{})
}, },
} }
var UpdateWebsiteDomain = &gormigrate.Migration{
ID: "20240808-update-website-domain",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.WebsiteDomain{})
},
}

View File

@ -67,14 +67,12 @@ export namespace Website {
} }
export interface WebSiteCreateReq { export interface WebSiteCreateReq {
primaryDomain: string;
type: string; type: string;
alias: string; alias: string;
remark: string; remark: string;
appType: string; appType: string;
appInstallId: number; appInstallId: number;
webSiteGroupId: number; webSiteGroupId: number;
otherDomains: string;
proxy: string; proxy: string;
proxyType: string; proxyType: string;
ftpUser: string; ftpUser: string;
@ -88,6 +86,7 @@ export namespace Website {
dbFormat?: string; dbFormat?: string;
dbUser?: string; dbUser?: string;
dbHost?: string; dbHost?: string;
domains: SubDomain[];
} }
export interface WebSiteUpdateReq { export interface WebSiteUpdateReq {
@ -128,7 +127,13 @@ export namespace Website {
export interface DomainCreate { export interface DomainCreate {
websiteID: number; websiteID: number;
domains: string; domains: SubDomain[];
}
interface SubDomain {
domain: string;
port: number;
ssl: boolean;
} }
export interface DomainDelete { export interface DomainDelete {
@ -288,7 +293,7 @@ export namespace Website {
SSLProtocol: string[]; SSLProtocol: string[];
algorithm: string; algorithm: string;
hsts: boolean; hsts: boolean;
httpsPort: number; httpsPort?: string;
} }
export interface CheckReq { export interface CheckReq {

View File

@ -2130,6 +2130,8 @@ const message = {
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.", "When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
createDb: 'Create Database', createDb: 'Create Database',
enableSSLHelper: 'Failure to enable will not affect the creation of the website', enableSSLHelper: 'Failure to enable will not affect the creation of the website',
batchAdd: 'Batch Add Domains',
generateDomain: 'Generate',
}, },
php: { php: {
short_open_tag: 'Short tag support', short_open_tag: 'Short tag support',

View File

@ -1980,6 +1980,8 @@ const message = {
sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI具體需要看 CDN 服務商文檔', sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI具體需要看 CDN 服務商文檔',
createDb: '建立資料庫', createDb: '建立資料庫',
enableSSLHelper: '開啟失敗不會影響網站創建', enableSSLHelper: '開啟失敗不會影響網站創建',
batchAdd: '批量添加域名',
generateDomain: '生成',
}, },
php: { php: {
short_open_tag: '短標簽支持', short_open_tag: '短標簽支持',

View File

@ -1982,6 +1982,8 @@ const message = {
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI具体需要看 CDN 服务商文档', sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI具体需要看 CDN 服务商文档',
createDb: '创建数据库', createDb: '创建数据库',
enableSSLHelper: '开启失败不会影响网站创建', enableSSLHelper: '开启失败不会影响网站创建',
batchAdd: '批量添加域名',
generateDomain: '生成',
}, },
php: { php: {
short_open_tag: '短标签支持', short_open_tag: '短标签支持',

View File

@ -1,30 +1,8 @@
<template> <template>
<el-drawer <DrawerPro v-model="open" :header="$t('website.addDomain')" :back="handleClose">
v-model="open" <el-form ref="domainForm" label-position="top" :model="create">
:close-on-click-modal="false" <DomainCreate v-model:form="create"></DomainCreate>
:close-on-press-escape="false"
:title="$t('website.addDomain')"
size="40%"
:before-close="handleClose"
>
<template #header>
<DrawerHeader :header="$t('website.addDomain')" :back="handleClose" />
</template>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form ref="domainForm" label-position="top" :model="domain" :rules="rules">
<el-form-item :label="$t('website.domain')" prop="domains">
<el-input
type="textarea"
:rows="3"
v-model="domain.domains"
:placeholder="$t('website.domainHelper')"
></el-input>
</el-form-item>
</el-form> </el-form>
</el-col>
</el-row>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button> <el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
@ -33,29 +11,31 @@
</el-button> </el-button>
</span> </span>
</template> </template>
</el-drawer> </DrawerPro>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import DrawerHeader from '@/components/drawer-header/index.vue';
import { CreateDomain } from '@/api/modules/website'; import { CreateDomain } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { ref } from 'vue'; import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import DomainCreate from '@/views/website/website/domain-create/index.vue';
const domainForm = ref<FormInstance>(); const domainForm = ref<FormInstance>();
const rules = ref({ const initDomain = () => ({
domains: [Rules.requiredInput], domain: '',
port: 80,
ssl: false,
}); });
const open = ref(false); const open = ref(false);
const loading = ref(false); const loading = ref(false);
const domain = ref({ const create = ref({
websiteID: 0, websiteID: 0,
domains: '', domains: [initDomain()],
domainStr: '',
}); });
const em = defineEmits(['close']); const em = defineEmits(['close']);
@ -66,7 +46,9 @@ const handleClose = () => {
}; };
const acceptParams = async (websiteId: number) => { const acceptParams = async (websiteId: number) => {
domain.value.websiteID = Number(websiteId); create.value.websiteID = Number(websiteId);
create.value.domains = [initDomain()];
create.value.domainStr = '';
open.value = true; open.value = true;
}; };
@ -77,7 +59,7 @@ const submit = async (formEl: FormInstance | undefined) => {
return; return;
} }
loading.value = true; loading.value = true;
CreateDomain(domain.value) CreateDomain(create.value)
.then(() => { .then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess')); MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
handleClose(); handleClose();

View File

@ -13,8 +13,8 @@
<el-switch v-model="form.enable" @change="changeEnable"></el-switch> <el-switch v-model="form.enable" @change="changeEnable"></el-switch>
</el-form-item> </el-form-item>
<div v-if="form.enable"> <div v-if="form.enable">
<el-form-item :label="'HTTPS ' + $t('commons.table.port')" prop="httpsPort"> <el-form-item :label="'HTTPS ' + $t('commons.table.port')" prop="HttpsPort">
<el-input v-model.number="form.httpsPort" /> <el-text>{{ form.httpsPort }}</el-text>
</el-form-item> </el-form-item>
<el-text type="warning" class="!ml-2">{{ $t('website.ipWebsiteWarn') }}</el-text> <el-text type="warning" class="!ml-2">{{ $t('website.ipWebsiteWarn') }}</el-text>
<el-divider content-position="left">{{ $t('website.SSLConfig') }}</el-divider> <el-divider content-position="left">{{ $t('website.SSLConfig') }}</el-divider>
@ -173,7 +173,7 @@ import { GetHTTPSConfig, ListSSL, SearchAcmeAccount, UpdateHTTPSConfig } from '@
import { ElMessageBox, FormInstance } from 'element-plus'; import { ElMessageBox, FormInstance } from 'element-plus';
import { computed, onMounted, reactive, ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { Rules, checkNumberRange } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util'; import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import FileList from '@/components/file-list/index.vue'; import FileList from '@/components/file-list/index.vue';
@ -204,7 +204,7 @@ const form = reactive({
algorithm: algorithm:
'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED', 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED',
SSLProtocol: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1'], SSLProtocol: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1'],
httpsPort: 443, httpsPort: '443',
}); });
const loading = ref(false); const loading = ref(false);
const ssls = ref(); const ssls = ref();
@ -222,7 +222,6 @@ const rules = ref({
SSLProtocol: [Rules.requiredSelect], SSLProtocol: [Rules.requiredSelect],
algorithm: [Rules.requiredInput], algorithm: [Rules.requiredInput],
acmeAccountID: [Rules.requiredInput], acmeAccountID: [Rules.requiredInput],
httpsPort: [Rules.requiredInput, checkNumberRange(1, 65535)],
}); });
const resData = ref(); const resData = ref();
const sslReq = reactive({ const sslReq = reactive({
@ -301,10 +300,8 @@ const get = () => {
form.acmeAccountID = data.SSL.acmeAccountId; form.acmeAccountID = data.SSL.acmeAccountId;
} }
form.hsts = data.hsts; form.hsts = data.hsts;
if (data.httpsPort > 0) {
form.httpsPort = data.httpsPort; form.httpsPort = data.httpsPort;
} }
}
listSSL(); listSSL();
listAcmeAccount(); listAcmeAccount();
}); });

View File

@ -262,21 +262,7 @@
<span class="input-help">{{ $t('app.allowPortHelper') }}</span> <span class="input-help">{{ $t('app.allowPortHelper') }}</span>
</el-form-item> </el-form-item>
</div> </div>
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain"> <DomainCreate v-model:form="website"></DomainCreate>
<el-input
v-model.trim="website.primaryDomain"
@input="changeAlias(website.primaryDomain)"
:placeholder="$t('website.primaryDomainHelper')"
></el-input>
</el-form-item>
<el-form-item :label="$t('website.otherDomains')" prop="otherDomains">
<el-input
type="textarea"
:rows="3"
v-model="website.otherDomains"
:placeholder="$t('website.domainHelper')"
></el-input>
</el-form-item>
<el-form-item prop="IPV6"> <el-form-item prop="IPV6">
<el-checkbox v-model="website.IPV6" :label="$t('website.ipv6')" size="large" /> <el-checkbox v-model="website.IPV6" :label="$t('website.ipv6')" size="large" />
</el-form-item> </el-form-item>
@ -517,7 +503,7 @@ import { ElForm, FormInstance } from 'element-plus';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import Params from '@/views/app-store/detail/params/index.vue'; import Params from '@/views/app-store/detail/params/index.vue';
import Check from '../check/index.vue'; import Check from '../check/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { GetGroupList } from '@/api/modules/group'; import { GetGroupList } from '@/api/modules/group';
import { Group } from '@/api/interface/group'; import { Group } from '@/api/interface/group';
import { SearchRuntimes } from '@/api/modules/runtime'; import { SearchRuntimes } from '@/api/modules/runtime';
@ -528,6 +514,7 @@ import { GetAppService } from '@/api/modules/app';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util'; import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util';
import { Website } from '@/api/interface/website'; import { Website } from '@/api/interface/website';
import DomainCreate from '@/views/website/website/domain-create/index.vue';
const websiteForm = ref<FormInstance>(); const websiteForm = ref<FormInstance>();
@ -577,6 +564,7 @@ const initData = () => ({
enableSSL: false, enableSSL: false,
websiteSSLID: undefined, websiteSSLID: undefined,
acmeAccountID: undefined, acmeAccountID: undefined,
domains: [],
}); });
const website = ref(initData()); const website = ref(initData());
const rules = ref<any>({ const rules = ref<any>({
@ -813,16 +801,6 @@ const changeAppType = (type: string) => {
} }
}; };
function isSubsetOfStrArray(primaryDomain: string, otherDomains: string): boolean {
const arr: string[] = otherDomains.split('\n');
for (const item of arr) {
if (primaryDomain === item) {
return false;
}
}
return true;
}
const openTaskLog = (taskID: string) => { const openTaskLog = (taskID: string) => {
taskLog.value.acceptParams(taskID); taskLog.value.acceptParams(taskID);
}; };
@ -859,12 +837,6 @@ const submit = async (formEl: FormInstance | undefined) => {
return; return;
} }
loading.value = true; loading.value = true;
const flag = isSubsetOfStrArray(website.value.primaryDomain, website.value.otherDomains);
if (!flag) {
MsgError(i18n.global.t('website.containWarn'));
loading.value = false;
return;
}
PreCheck({}) PreCheck({})
.then((res) => { .then((res) => {
if (res.data) { if (res.data) {
@ -897,6 +869,17 @@ const submit = async (formEl: FormInstance | undefined) => {
}); });
}; };
watch(
() => website.value.domains,
(value) => {
if (value.length > 0) {
const firstDomain = value[0].domain;
changeAlias(firstDomain);
}
},
{ deep: true },
);
const changeAlias = (value: string) => { const changeAlias = (value: string) => {
const domain = value.split(':')[0]; const domain = value.split(':')[0];
website.value.alias = domain; website.value.alias = domain;

View File

@ -0,0 +1,138 @@
<template>
<div>
<el-form-item :label="$t('website.batchAdd')">
<el-row :gutter="20">
<el-col :span="20">
<el-input
class="p-w-400"
type="textarea"
:rows="3"
v-model="create.domainStr"
:placeholder="$t('website.domainHelper')"
></el-input>
</el-col>
<el-col :span="4">
<el-button @click="gengerateDomains" :disabled="create.domainStr == ''">
{{ $t('website.generateDomain') }}
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-row :gutter="20" v-for="(domain, index) of create.domains" :key="index">
<el-col :span="8">
<el-form-item
:label="index == 0 ? $t('website.domain') : ''"
:prop="`domains.${index}.domain`"
:rules="rules.domain"
>
<el-input
type="string"
v-model="create.domains[index].domain"
:placeholder="index > 0 ? $t('website.domain') : ''"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
:label="index == 0 ? $t('commons.table.port') : ''"
:prop="`domains.${index}.port`"
:rules="rules.port"
>
<el-input type="number" v-model.number="create.domains[index].port"></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="index == 0 ? 'SSL' : ''" prop="ssl">
<el-checkbox
v-model="create.domains[index].ssl"
:disabled="create.domains[index].port == 80"
></el-checkbox>
</el-form-item>
</el-col>
<el-col :span="4" v-if="index == 0">
<el-form-item :label="$t('commons.button.add') + $t('commons.table.port')">
<el-button @click="addDomain">
<el-icon><Plus /></el-icon>
</el-button>
</el-form-item>
</el-col>
<el-col :span="4" v-else>
<el-form-item>
<el-button @click="removeDomain(index)" link type="primary">
<el-icon><Delete /></el-icon>
</el-button>
</el-form-item>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { Rules, checkNumberRange } from '@/global/form-rules';
import { ref } from 'vue';
const props = defineProps({
form: {
type: Object,
default: function () {
return {};
},
},
});
const rules = ref({
port: [Rules.requiredInput, Rules.paramPort, checkNumberRange(1, 65535)],
domain: [Rules.requiredInput, Rules.domain],
domains: {
type: Array,
},
});
const initDomain = () => ({
domain: '',
port: 80,
ssl: false,
});
const create = ref({
websiteID: 0,
domains: [initDomain()],
domainStr: '',
});
const addDomain = () => {
create.value.domains.push(initDomain());
};
const removeDomain = (index: number) => {
create.value.domains.splice(index, 1);
};
const gengerateDomains = () => {
const lines = create.value.domainStr.split(/\r?\n/);
lines.forEach((line) => {
const [domain, port] = line.split(':');
const exists = (domain: string, port: number): boolean => {
return create.value.domains.some((info) => info.domain === domain && info.port === port);
};
if (exists(domain, port ? Number(port) : 80)) {
return;
}
if (create.value.domains[0].domain == '') {
create.value.domains[0].domain = domain;
create.value.domains[0].port = port ? Number(port) : 80;
} else {
create.value.domains.push({
domain,
port: port ? Number(port) : 80,
ssl: false,
});
}
});
};
const handleParams = () => {
props.form.domains = create.value.domains;
};
onMounted(() => {
handleParams();
});
</script>