mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: The website supports TCP/UDP proxying. (#11302)
Refs https://github.com/1Panel-dev/1Panel/issues/5135 Refs https://github.com/1Panel-dev/1Panel/issues/6976
This commit is contained in:
@@ -1223,3 +1223,23 @@ func (b *BaseApi) UpdateCORSConfig(c *gin.Context) {
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Update Stream Config
|
||||
// @Accept json
|
||||
// @Param request body request.StreamUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/stream/update [post]
|
||||
func (b *BaseApi) UpdateStreamConfig(c *gin.Context) {
|
||||
var req request.StreamUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateStream(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,21 @@ type WebsiteCreate struct {
|
||||
FtpConfig
|
||||
DataBaseConfig
|
||||
SSLConfig
|
||||
StreamConfig
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
StreamPorts string `json:"streamPorts" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
|
||||
Servers []dto.NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type StreamUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
|
||||
StreamConfig
|
||||
}
|
||||
|
||||
type WebsiteOptionReq struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package response
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
|
||||
@@ -16,6 +17,9 @@ type WebsiteDTO struct {
|
||||
RuntimeType string `json:"runtimeType"`
|
||||
SiteDir string `json:"siteDir"`
|
||||
OpenBaseDir bool `json:"openBaseDir"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
|
||||
Servers []dto.NginxUpstreamServer `json:"servers"`
|
||||
}
|
||||
|
||||
type WebsiteRes struct {
|
||||
|
||||
@@ -37,6 +37,8 @@ type Website struct {
|
||||
|
||||
Favorite bool `json:"favorite"`
|
||||
|
||||
StreamPorts string `json:"streamPorts"`
|
||||
|
||||
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
|
||||
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
|
||||
if website != nil {
|
||||
nginxFull.Website = *website
|
||||
var siteNginxConfig dto.NginxConfig
|
||||
siteConfigPath := GetSitePath(*website, SiteConf)
|
||||
siteConfigPath := GetWebsiteConfigPath(*website)
|
||||
siteNginxConfig.FilePath = siteConfigPath
|
||||
siteNginxContent, err := os.ReadFile(siteConfigPath)
|
||||
if err != nil {
|
||||
|
||||
+168
-1237
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"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/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
authContent []byte
|
||||
nginxParams []response.NginxParam
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(absoluteAuthPath) {
|
||||
return
|
||||
}
|
||||
nginxParams, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"auth_basic"}, &website)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.Enable = len(nginxParams[0].Params) > 0
|
||||
authContent, err = fileOp.GetContent(absoluteAuthPath)
|
||||
authArray := strings.Split(string(authContent), "\n")
|
||||
for _, line := range authArray {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
params := strings.Split(line, ":")
|
||||
auth := dto.NginxAuth{
|
||||
Username: params[0],
|
||||
}
|
||||
if len(params) == 3 {
|
||||
auth.Remark = params[2]
|
||||
}
|
||||
res.Items = append(res.Items, auth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
params []dto.NginxParam
|
||||
authContent []byte
|
||||
authArray []string
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias)
|
||||
absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(path.Dir(absoluteAuthPath)) {
|
||||
_ = fileOp.CreateDir(path.Dir(absoluteAuthPath), constant.DirPerm)
|
||||
}
|
||||
if !fileOp.Stat(absoluteAuthPath) {
|
||||
_ = fileOp.CreateFile(absoluteAuthPath)
|
||||
}
|
||||
|
||||
params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
|
||||
params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
|
||||
authContent, err = fileOp.GetContent(absoluteAuthPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(authContent) > 0 {
|
||||
authArray = strings.Split(string(authContent), "\n")
|
||||
}
|
||||
switch req.Operate {
|
||||
case "disable":
|
||||
return deleteNginxConfig(constant.NginxScopeServer, params, &website)
|
||||
case "enable":
|
||||
return updateNginxConfig(constant.NginxScopeServer, params, &website)
|
||||
case "create":
|
||||
for _, line := range authArray {
|
||||
authParams := strings.Split(line, ":")
|
||||
username := authParams[0]
|
||||
if username == req.Username {
|
||||
err = buserr.New("ErrUsernameIsExist")
|
||||
return
|
||||
}
|
||||
}
|
||||
var passwdHash []byte
|
||||
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
|
||||
if req.Remark != "" {
|
||||
line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
|
||||
}
|
||||
authArray = append(authArray, line)
|
||||
case "edit":
|
||||
userExist := false
|
||||
for index, line := range authArray {
|
||||
authParams := strings.Split(line, ":")
|
||||
username := authParams[0]
|
||||
if username == req.Username {
|
||||
userExist = true
|
||||
var passwdHash []byte
|
||||
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userPasswd := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
|
||||
if req.Remark != "" {
|
||||
userPasswd = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
|
||||
}
|
||||
authArray[index] = userPasswd
|
||||
}
|
||||
}
|
||||
if !userExist {
|
||||
err = buserr.New("ErrUsernameIsNotExist")
|
||||
return
|
||||
}
|
||||
case "delete":
|
||||
deleteIndex := -1
|
||||
for index, line := range authArray {
|
||||
authParams := strings.Split(line, ":")
|
||||
username := authParams[0]
|
||||
if username == req.Username {
|
||||
deleteIndex = index
|
||||
}
|
||||
}
|
||||
if deleteIndex < 0 {
|
||||
return
|
||||
}
|
||||
authArray = append(authArray[:deleteIndex], authArray[deleteIndex+1:]...)
|
||||
}
|
||||
|
||||
var passFile *os.File
|
||||
passFile, err = os.Create(absoluteAuthPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer passFile.Close()
|
||||
writer := bufio.NewWriter(passFile)
|
||||
for _, line := range authArray {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
_, err = writer.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
authContent, err = fileOp.GetContent(absoluteAuthPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(authContent) == 0 {
|
||||
if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
authContent []byte
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
absoluteAuthDir := GetSitePath(website, SitePathAuthBasicDir)
|
||||
passDir := path.Join(absoluteAuthDir, "pass")
|
||||
if !fileOp.Stat(absoluteAuthDir) || !fileOp.Stat(passDir) {
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(absoluteAuthDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
name := strings.TrimSuffix(entry.Name(), ".conf")
|
||||
pathAuth := dto.NginxPathAuth{
|
||||
Name: name,
|
||||
}
|
||||
configPath := path.Join(absoluteAuthDir, entry.Name())
|
||||
content, err := fileOp.GetContent(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directives := config.Directives
|
||||
location, _ := directives[0].(*components.Location)
|
||||
pathAuth.Path = strings.TrimPrefix(location.Match, "^")
|
||||
passPath := path.Join(passDir, fmt.Sprintf("%s.pass", name))
|
||||
authContent, err = fileOp.GetContent(passPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authArray := strings.Split(string(authContent), "\n")
|
||||
for _, line := range authArray {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
params := strings.Split(line, ":")
|
||||
pathAuth.Username = params[0]
|
||||
if len(params) == 3 {
|
||||
pathAuth.Remark = params[2]
|
||||
}
|
||||
}
|
||||
res = append(res, response.NginxPathAuthRes{
|
||||
NginxPathAuth: pathAuth,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
authDir := GetSitePath(website, SitePathAuthBasicDir)
|
||||
if !fileOp.Stat(authDir) {
|
||||
_ = fileOp.CreateDir(authDir, constant.DirPerm)
|
||||
}
|
||||
passDir := path.Join(authDir, "pass")
|
||||
if !fileOp.Stat(passDir) {
|
||||
_ = fileOp.CreateDir(passDir, constant.DirPerm)
|
||||
}
|
||||
confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name))
|
||||
var config *components.Config
|
||||
switch req.Operate {
|
||||
case "delete":
|
||||
_ = fileOp.DeleteFile(confPath)
|
||||
_ = fileOp.DeleteFile(passPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "create":
|
||||
config, err = parser.NewStringParser(string(nginx_conf.PathAuth)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileOp.Stat(confPath) || fileOp.Stat(passPath) {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
case "edit":
|
||||
par, err := parser.NewParser(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err = par.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config.FilePath = confPath
|
||||
directives := config.Directives
|
||||
location, _ := directives[0].(*components.Location)
|
||||
location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))})
|
||||
location.ChangePath("~*", fmt.Sprintf("^%s", req.Path))
|
||||
var passwdHash []byte
|
||||
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
|
||||
if req.Remark != "" {
|
||||
line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
|
||||
}
|
||||
_ = fileOp.SaveFile(passPath, line, constant.DirPerm)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) {
|
||||
var (
|
||||
domainModels []model.WebsiteDomain
|
||||
addPorts []int
|
||||
)
|
||||
httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(create.WebsiteID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
_ = OperateFirewallPort(nil, addPorts)
|
||||
}()
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(wafDataPath) {
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
if content != nil {
|
||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, wafWebsite := range websitesArray {
|
||||
if wafWebsite.Key == website.Alias {
|
||||
wafSite := request.WafWebsite{
|
||||
Key: website.Alias,
|
||||
Domains: wafWebsite.Domains,
|
||||
Host: wafWebsite.Host,
|
||||
}
|
||||
for _, domain := range domainModels {
|
||||
wafSite.Domains = append(wafSite.Domains, domain.Domain)
|
||||
wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
|
||||
}
|
||||
if len(wafSite.Host) == 0 {
|
||||
wafSite.Host = []string{}
|
||||
}
|
||||
websitesArray[index] = wafSite
|
||||
break
|
||||
}
|
||||
}
|
||||
websitesContent, err := json.Marshal(websitesArray)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = addListenAndServerName(website, domainModels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels)
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) {
|
||||
return websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId))
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
webSiteDomain, err := websiteDomainRepo.GetFirst(repo.WithByID(domainId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID)); len(websiteDomains) == 1 {
|
||||
return fmt.Errorf("can not delete last domain")
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(webSiteDomain.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ports []int
|
||||
if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithPort(webSiteDomain.Port)); len(oldDomains) == 1 {
|
||||
ports = append(ports, webSiteDomain.Port)
|
||||
}
|
||||
|
||||
var domains []string
|
||||
if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 {
|
||||
domains = append(domains, webSiteDomain.Domain)
|
||||
}
|
||||
|
||||
if len(ports) > 0 || len(domains) > 0 {
|
||||
stringBinds := make([]string, len(ports))
|
||||
for i := 0; i < len(ports); i++ {
|
||||
stringBinds[i] = strconv.Itoa(ports[i])
|
||||
}
|
||||
if err := deleteListenAndServerName(website, stringBinds, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(wafDataPath) {
|
||||
websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
|
||||
content, err := fileOp.GetContent(websitesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var websitesArray []request.WafWebsite
|
||||
var newWebsitesArray []request.WafWebsite
|
||||
if content != nil {
|
||||
if err := json.Unmarshal(content, &websitesArray); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, wafWebsite := range websitesArray {
|
||||
if wafWebsite.Key == website.Alias {
|
||||
wafSite := wafWebsite
|
||||
oldDomains := wafSite.Domains
|
||||
var newDomains []string
|
||||
for _, domain := range oldDomains {
|
||||
if domain == webSiteDomain.Domain {
|
||||
continue
|
||||
}
|
||||
newDomains = append(newDomains, domain)
|
||||
}
|
||||
wafSite.Domains = newDomains
|
||||
oldHostArray := wafSite.Host
|
||||
var newHostArray []string
|
||||
for _, host := range oldHostArray {
|
||||
if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) {
|
||||
continue
|
||||
}
|
||||
newHostArray = append(newHostArray, host)
|
||||
}
|
||||
wafSite.Host = newHostArray
|
||||
if len(wafSite.Host) == 0 {
|
||||
wafSite.Host = []string{}
|
||||
}
|
||||
newWebsitesArray = append(newWebsitesArray, wafSite)
|
||||
} else {
|
||||
newWebsitesArray = append(newWebsitesArray, wafWebsite)
|
||||
}
|
||||
}
|
||||
websitesContent, err := json.Marshal(newWebsitesArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return websiteDomainRepo.DeleteBy(context.TODO(), repo.WithByID(domainId))
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
return nil, nil
|
||||
}
|
||||
entries, err := os.ReadDir(includeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []dto.NginxUpstream
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasSuffix(name, ".conf") {
|
||||
continue
|
||||
}
|
||||
upstreamName := strings.TrimSuffix(name, ".conf")
|
||||
upstream := dto.NginxUpstream{
|
||||
Name: upstreamName,
|
||||
}
|
||||
upstreamPath := path.Join(includeDir, name)
|
||||
content, err := fileOp.GetContent(upstreamPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstream.Content = string(content)
|
||||
nginxParser, err := parser.NewParser(upstreamPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := nginxParser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreams := config.FindUpstreams()
|
||||
for _, up := range upstreams {
|
||||
if up.UpstreamName == upstreamName {
|
||||
directives := up.GetDirectives()
|
||||
for _, d := range directives {
|
||||
dName := d.GetName()
|
||||
if _, ok := dto.LBAlgorithms[dName]; ok {
|
||||
upstream.Algorithm = dName
|
||||
}
|
||||
}
|
||||
upstream.Servers = getNginxUpstreamServers(up.UpstreamServers)
|
||||
}
|
||||
}
|
||||
res = append(res, upstream)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, constant.DirPerm)
|
||||
}
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if fileOp.Stat(filePath) {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Block = &components.Block{}
|
||||
config.FilePath = filePath
|
||||
upstream := components.Upstream{
|
||||
UpstreamName: req.Name,
|
||||
}
|
||||
if req.Algorithm != "default" {
|
||||
upstream.UpdateDirective(req.Algorithm, []string{})
|
||||
}
|
||||
upstream.UpstreamServers = parseUpstreamServers(req.Servers)
|
||||
config.Block.Directives = append(config.Block.Directives, &upstream)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.DeleteFile(filePath)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias)
|
||||
if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if !fileOp.Stat(filePath) {
|
||||
return nil
|
||||
}
|
||||
oldContent, err := fileOp.GetContent(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parser, err := parser.NewParser(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upstreams := config.FindUpstreams()
|
||||
for _, up := range upstreams {
|
||||
if up.UpstreamName == req.Name {
|
||||
directives := up.GetDirectives()
|
||||
for _, d := range directives {
|
||||
dName := d.GetName()
|
||||
if _, ok := dto.LBAlgorithms[dName]; ok {
|
||||
up.RemoveDirective(dName, nil)
|
||||
}
|
||||
}
|
||||
if req.Algorithm != "default" {
|
||||
up.UpdateDirective(req.Algorithm, []string{})
|
||||
}
|
||||
up.UpstreamServers = parseUpstreamServers(req.Servers)
|
||||
}
|
||||
}
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxies, _ := w.GetProxies(website.ID)
|
||||
if len(proxies) > 0 {
|
||||
for _, proxy := range proxies {
|
||||
if strings.HasSuffix(proxy.ProxyPass, fmt.Sprintf("://%s", req.Name)) {
|
||||
return buserr.New("ErrProxyIsUsed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
fileOp := files.NewFileOp()
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
if !fileOp.Stat(filePath) {
|
||||
return nil
|
||||
}
|
||||
if err = fileOp.DeleteFile(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
return opNginx(nginxInstall.ContainerName, constant.NginxReload)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteUpstreamDir)
|
||||
filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
fileOp := files.NewFileOp()
|
||||
oldContent, err := fileOp.GetContent(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), constant.DirPerm)
|
||||
}
|
||||
}()
|
||||
return opNginx(nginxInstall.ContainerName, constant.NginxReload)
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"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/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
par *parser.Parser
|
||||
oldContent []byte
|
||||
)
|
||||
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := GetSitePath(website, SiteProxyDir)
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, constant.DirPerm)
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
|
||||
if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
|
||||
err = buserr.New("ErrNameIsExist")
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
case "edit":
|
||||
_ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), constant.DirPerm)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var config *components.Config
|
||||
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
config, err = parser.NewStringParser(string(nginx_conf.GetWebsiteFile("proxy.conf"))).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "edit":
|
||||
par, err = parser.NewParser(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config, err = par.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldContent, err = fileOp.GetContent(includePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "delete":
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "disable":
|
||||
_ = fileOp.Rename(includePath, backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
case "enable":
|
||||
_ = fileOp.Rename(backPath, includePath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
|
||||
var location *components.Location
|
||||
for _, directive := range directives {
|
||||
if loc, ok := directive.(*components.Location); ok {
|
||||
location = loc
|
||||
break
|
||||
}
|
||||
}
|
||||
if location == nil {
|
||||
err = errors.New("invalid proxy config, no location found")
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
|
||||
location.ChangePath(req.Modifier, req.Match)
|
||||
// Server Cache Settings
|
||||
if req.Cache {
|
||||
if err = openProxyCache(website); err != nil {
|
||||
return
|
||||
}
|
||||
location.AddServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias), req.ServerCacheTime, req.ServerCacheUnit)
|
||||
} else {
|
||||
location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
|
||||
}
|
||||
// Browser Cache Settings
|
||||
if req.CacheTime != 0 {
|
||||
location.AddBrowserCache(req.CacheTime, req.CacheUnit)
|
||||
} else {
|
||||
location.RemoveBrowserCache()
|
||||
}
|
||||
// Content Replace Settings
|
||||
if len(req.Replaces) > 0 {
|
||||
location.AddSubFilter(req.Replaces)
|
||||
} else {
|
||||
location.RemoveSubFilter()
|
||||
}
|
||||
// SSL Settings
|
||||
if req.SNI {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
|
||||
if req.ProxySSLName != "" {
|
||||
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
|
||||
}
|
||||
} else {
|
||||
location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
|
||||
}
|
||||
// CORS Settings
|
||||
if req.Cors {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
|
||||
if req.AllowMethods != "" {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
|
||||
}
|
||||
if req.AllowHeaders != "" {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
|
||||
}
|
||||
if req.AllowCredentials {
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
|
||||
}
|
||||
if req.Preflight {
|
||||
location.AddCorsOption()
|
||||
} else {
|
||||
location.RemoveCorsOption()
|
||||
}
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Origin"})
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
|
||||
location.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"})
|
||||
}
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cacheDir := GetSitePath(website, SiteCacheDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(cacheDir) {
|
||||
_ = fileOp.CreateDir(cacheDir, constant.DirPerm)
|
||||
}
|
||||
if req.Open {
|
||||
proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:%d%s max_size=%d%s inactive=%d%s", website.Alias, website.Alias, req.ShareCache, req.ShareCacheUnit, req.CacheLimit, req.CacheLimitUnit, req.CacheExpire, req.CacheExpireUnit)
|
||||
return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
|
||||
}
|
||||
return deleteNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path"}}, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetProxyCache(id uint) (res response.NginxProxyCache, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
parser, err := parser.NewParser(GetSitePath(website, SiteConf))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config, err := parser.Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var params []string
|
||||
for _, d := range config.GetDirectives() {
|
||||
if d.GetName() == "proxy_cache_path" {
|
||||
params = d.GetParameters()
|
||||
}
|
||||
}
|
||||
if len(params) == 0 {
|
||||
return
|
||||
}
|
||||
for _, param := range params {
|
||||
if re.GetRegex(re.ProxyCacheZonePattern).MatchString(param) {
|
||||
matches := re.GetRegex(re.ProxyCacheZonePattern).FindStringSubmatch(param)
|
||||
if len(matches) > 0 {
|
||||
res.ShareCache, _ = strconv.Atoi(matches[1])
|
||||
res.ShareCacheUnit = matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
if re.GetRegex(re.ProxyCacheMaxSizeValidationPattern).MatchString(param) {
|
||||
matches := re.GetRegex(re.ProxyCacheMaxSizePattern).FindStringSubmatch(param)
|
||||
if len(matches) > 0 {
|
||||
res.CacheLimit, _ = strconv.ParseFloat(matches[1], 64)
|
||||
res.CacheLimitUnit = matches[2]
|
||||
}
|
||||
}
|
||||
if re.GetRegex(re.ProxyCacheInactivePattern).MatchString(param) {
|
||||
matches := re.GetRegex(re.ProxyCacheInactivePattern).FindStringSubmatch(param)
|
||||
if len(matches) > 0 {
|
||||
res.CacheExpire, _ = strconv.Atoi(matches[1])
|
||||
res.CacheExpireUnit = matches[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
res.Open = true
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
fileList response.FileInfo
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := GetSitePath(website, SiteProxyDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(includeDir) {
|
||||
return
|
||||
}
|
||||
fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
|
||||
if len(fileList.Items) == 0 {
|
||||
return
|
||||
}
|
||||
var (
|
||||
content []byte
|
||||
config *components.Config
|
||||
)
|
||||
for _, configFile := range fileList.Items {
|
||||
proxyConfig := request.WebsiteProxyConfig{
|
||||
ID: website.ID,
|
||||
}
|
||||
parts := strings.Split(configFile.Name, ".")
|
||||
proxyConfig.Name = parts[0]
|
||||
if parts[1] == "conf" {
|
||||
proxyConfig.Enable = true
|
||||
} else {
|
||||
proxyConfig.Enable = false
|
||||
}
|
||||
proxyConfig.FilePath = configFile.Path
|
||||
content, err = fileOp.GetContent(configFile.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
proxyConfig.Content = string(content)
|
||||
config, err = parser.NewStringParser(string(content)).Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
var location *components.Location
|
||||
for _, directive := range directives {
|
||||
if loc, ok := directive.(*components.Location); ok {
|
||||
location = loc
|
||||
break
|
||||
}
|
||||
}
|
||||
if location == nil {
|
||||
err = errors.New("invalid proxy config, no location found")
|
||||
return
|
||||
}
|
||||
proxyConfig.ProxyPass = location.ProxyPass
|
||||
proxyConfig.Cache = location.Cache
|
||||
if location.CacheTime > 0 {
|
||||
proxyConfig.CacheTime = location.CacheTime
|
||||
proxyConfig.CacheUnit = location.CacheUint
|
||||
}
|
||||
if location.ServerCacheTime > 0 {
|
||||
proxyConfig.ServerCacheTime = location.ServerCacheTime
|
||||
proxyConfig.ServerCacheUnit = location.ServerCacheUint
|
||||
}
|
||||
proxyConfig.Match = location.Match
|
||||
proxyConfig.Modifier = location.Modifier
|
||||
proxyConfig.ProxyHost = location.Host
|
||||
proxyConfig.Replaces = location.Replaces
|
||||
for _, directive := range location.Directives {
|
||||
if directive.GetName() == "proxy_ssl_server_name" {
|
||||
proxyConfig.SNI = directive.GetParameters()[0] == "on"
|
||||
}
|
||||
if directive.GetName() == "proxy_ssl_name" && len(directive.GetParameters()) > 0 {
|
||||
proxyConfig.ProxySSLName = directive.GetParameters()[0]
|
||||
}
|
||||
}
|
||||
proxyConfig.Cors = location.Cors
|
||||
proxyConfig.AllowCredentials = location.AllowCredentials
|
||||
proxyConfig.AllowHeaders = location.AllowHeaders
|
||||
proxyConfig.AllowOrigins = location.AllowOrigins
|
||||
proxyConfig.AllowMethods = location.AllowMethods
|
||||
proxyConfig.Preflight = location.Preflight
|
||||
res = append(res, proxyConfig)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) {
|
||||
var (
|
||||
website model.Website
|
||||
oldRewriteContent []byte
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
absolutePath := fmt.Sprintf("%s/%s.conf", GetSitePath(website, SiteProxyDir), req.Name)
|
||||
fileOp := files.NewFileOp()
|
||||
oldRewriteContent, err = fileOp.GetContent(absolutePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
|
||||
}
|
||||
}()
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheDir := GetSitePath(website, SiteCacheDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(cacheDir) {
|
||||
if err = fileOp.CleanDir(cacheDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
_ = fileOp.CreateDir(includeDir, 0755)
|
||||
}
|
||||
fileName := fmt.Sprintf("%s.conf", req.Name)
|
||||
includePath := path.Join(includeDir, fileName)
|
||||
backName := fmt.Sprintf("%s.bak", req.Name)
|
||||
backPath := path.Join(includeDir, backName)
|
||||
_ = fileOp.DeleteFile(includePath)
|
||||
_ = fileOp.DeleteFile(backPath)
|
||||
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"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/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
includePath := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
|
||||
absolutePath := GetSitePath(website, SiteReWritePath)
|
||||
fileOp := files.NewFileOp()
|
||||
var oldRewriteContent []byte
|
||||
if !fileOp.Stat(path.Dir(absolutePath)) {
|
||||
if err := fileOp.CreateDir(path.Dir(absolutePath), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !fileOp.Stat(absolutePath) {
|
||||
if err := fileOp.CreateFile(absolutePath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
oldRewriteContent, err = fileOp.GetContent(absolutePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{includePath}}}, &website); err != nil {
|
||||
_ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
|
||||
return err
|
||||
}
|
||||
website.Rewrite = req.Name
|
||||
return websiteRepo.Save(context.Background(), &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) {
|
||||
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var contentByte []byte
|
||||
if req.Name == "current" {
|
||||
rewriteConfPath := GetSitePath(website, SiteReWritePath)
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(rewriteConfPath) {
|
||||
contentByte, err = fileOp.GetContent(rewriteConfPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rewriteFile := fmt.Sprintf("rewrite/%s.conf", strings.ToLower(req.Name))
|
||||
contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile)
|
||||
if contentByte == nil {
|
||||
customRewriteDir := GetOpenrestyDir(DefaultRewriteDir)
|
||||
customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name)))
|
||||
contentByte, err = files.NewFileOp().GetContent(customRewriteFile)
|
||||
}
|
||||
}
|
||||
return &response.NginxRewriteRes{
|
||||
Content: string(contentByte),
|
||||
}, err
|
||||
}
|
||||
|
||||
func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) error {
|
||||
rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(rewriteDir) {
|
||||
if err := fileOp.CreateDir(rewriteDir, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name))
|
||||
switch req.Operate {
|
||||
case "create":
|
||||
if fileOp.Stat(rewriteFile) {
|
||||
return buserr.New("ErrNameIsExist")
|
||||
}
|
||||
return fileOp.WriteFile(rewriteFile, strings.NewReader(req.Content), constant.DirPerm)
|
||||
case "delete":
|
||||
return fileOp.DeleteFile(rewriteFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) ListCustomRewrite() ([]string, error) {
|
||||
rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(rewriteDir) {
|
||||
return nil, nil
|
||||
}
|
||||
entries, err := os.ReadDir(rewriteDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
res = append(res, strings.TrimSuffix(entry.Name(), ".conf"))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
+185
-103
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
@@ -16,8 +17,6 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -144,11 +143,13 @@ func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
|
||||
if err := fileOp.CreateFile(path.Join(siteFolder, "log", "error.log")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil {
|
||||
return err
|
||||
if website.Type != constant.Stream {
|
||||
if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if website.Type == constant.Runtime {
|
||||
if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal {
|
||||
@@ -175,7 +176,7 @@ func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime) error {
|
||||
func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, streamConfig request.StreamConfig) error {
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,72 +184,83 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
if err = createWebsiteFolder(website, runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
configPath := GetSitePath(*website, SiteConf)
|
||||
nginxContent := nginx_conf.GetWebsiteFile("website_default.conf")
|
||||
config, err := parser.NewStringParser(string(nginxContent)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers := config.FindServers()
|
||||
if len(servers) == 0 {
|
||||
return errors.New("nginx config is not valid")
|
||||
}
|
||||
server := servers[0]
|
||||
server.DeleteListen("80")
|
||||
var serverNames []string
|
||||
for _, domain := range domains {
|
||||
serverNames = append(serverNames, domain.Domain)
|
||||
setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false)
|
||||
}
|
||||
server.UpdateServerName(serverNames)
|
||||
|
||||
siteFolder := path.Join("/www", "sites", website.Alias)
|
||||
server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "main"})
|
||||
server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
|
||||
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
case constant.Static:
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
case constant.Proxy:
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
server.UpdateDirective("include", []string{nginxInclude})
|
||||
server.UpdateRoot(rootIndex)
|
||||
case constant.Runtime:
|
||||
switch runtime.Type {
|
||||
case constant.RuntimePHP:
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
if runtime.Resource == constant.ResourceLocal {
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := path.Join(rootIndex, "index.php")
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
} else {
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)})
|
||||
}
|
||||
case constant.Subsite:
|
||||
parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
|
||||
var (
|
||||
configPath string
|
||||
config *components.Config
|
||||
)
|
||||
if website.Type == constant.Stream {
|
||||
nginxContent := nginx_conf.GetWebsiteFile("stream_default.conf")
|
||||
config, err = parser.NewStringParser(string(nginxContent)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website.Proxy = parentWebsite.Proxy
|
||||
rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir)
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
if parentWebsite.Type == constant.Runtime {
|
||||
parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
servers := config.FindServers()
|
||||
if len(servers) == 0 {
|
||||
return errors.New("nginx config is not valid")
|
||||
}
|
||||
server := servers[0]
|
||||
ports := strings.Split(streamConfig.StreamPorts, ",")
|
||||
for _, port := range ports {
|
||||
server.UpdateListen(port, false)
|
||||
if website.IPV6 {
|
||||
server.UpdateListen("[::]:"+port, false)
|
||||
}
|
||||
website.RuntimeID = parentRuntime.ID
|
||||
if parentRuntime.Type == constant.RuntimePHP {
|
||||
if parentRuntime.Resource == constant.ResourceLocal {
|
||||
}
|
||||
siteFolder := path.Join("/www", "sites", website.Alias)
|
||||
server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "streamlog"})
|
||||
server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
|
||||
server.UpdateDirective("proxy_pass", []string{website.Alias})
|
||||
|
||||
upstream := components.Upstream{
|
||||
UpstreamName: website.Alias,
|
||||
}
|
||||
if streamConfig.Algorithm != "default" {
|
||||
upstream.UpdateDirective(streamConfig.Algorithm, []string{})
|
||||
}
|
||||
upstream.UpstreamServers = parseUpstreamServers(streamConfig.Servers)
|
||||
config.Block.Directives = append(config.Block.Directives, &upstream)
|
||||
configPath = GetSitePath(*website, StreamConf)
|
||||
} else {
|
||||
configPath = GetSitePath(*website, SiteConf)
|
||||
nginxContent := nginx_conf.GetWebsiteFile("website_default.conf")
|
||||
config, err = parser.NewStringParser(string(nginxContent)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers := config.FindServers()
|
||||
if len(servers) == 0 {
|
||||
return errors.New("nginx config is not valid")
|
||||
}
|
||||
server := servers[0]
|
||||
server.DeleteListen("80")
|
||||
var serverNames []string
|
||||
for _, domain := range domains {
|
||||
serverNames = append(serverNames, domain.Domain)
|
||||
setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false)
|
||||
}
|
||||
server.UpdateServerName(serverNames)
|
||||
|
||||
siteFolder := path.Join("/www", "sites", website.Alias)
|
||||
server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "main"})
|
||||
server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
|
||||
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
case constant.Static:
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
case constant.Proxy:
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
server.UpdateDirective("include", []string{nginxInclude})
|
||||
server.UpdateRoot(rootIndex)
|
||||
case constant.Runtime:
|
||||
switch runtime.Type {
|
||||
case constant.RuntimePHP:
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
if runtime.Resource == constant.ResourceLocal {
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := path.Join(rootIndex, "index.php")
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
@@ -256,13 +268,39 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)})
|
||||
}
|
||||
case constant.Subsite:
|
||||
parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website.Proxy = parentWebsite.Proxy
|
||||
rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir)
|
||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||
if parentWebsite.Type == constant.Runtime {
|
||||
parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website.RuntimeID = parentRuntime.ID
|
||||
if parentRuntime.Type == constant.RuntimePHP {
|
||||
if parentRuntime.Resource == constant.ResourceLocal {
|
||||
server.UpdateRoot(rootIndex)
|
||||
localPath := path.Join(rootIndex, "index.php")
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
} else {
|
||||
server.UpdateRoot(rootIndex)
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
if parentWebsite.Type == constant.Static {
|
||||
server.UpdateRoot(rootIndex)
|
||||
}
|
||||
}
|
||||
if parentWebsite.Type == constant.Static {
|
||||
server.UpdateRoot(rootIndex)
|
||||
}
|
||||
}
|
||||
|
||||
config.FilePath = configPath
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return err
|
||||
@@ -432,14 +470,20 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
|
||||
}
|
||||
|
||||
func delNginxConfig(website model.Website, force bool) error {
|
||||
configPath := GetSitePath(website, SiteConf)
|
||||
fileOp := files.NewFileOp()
|
||||
var (
|
||||
configPath string
|
||||
)
|
||||
if website.Type == constant.Stream {
|
||||
configPath = GetSitePath(website, StreamConf)
|
||||
|
||||
if !fileOp.Stat(configPath) {
|
||||
return nil
|
||||
} else {
|
||||
configPath = GetSitePath(website, SiteConf)
|
||||
}
|
||||
if err := fileOp.DeleteFile(configPath); err != nil {
|
||||
return err
|
||||
if fileOp.Stat(configPath) {
|
||||
if err := fileOp.DeleteFile(configPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sitePath := GteSiteDir(website.Alias)
|
||||
if fileOp.Stat(sitePath) {
|
||||
@@ -880,9 +924,16 @@ func deleteWebsiteFolder(website *model.Website) error {
|
||||
if fileOp.Stat(siteFolder) {
|
||||
_ = fileOp.DeleteDir(siteFolder)
|
||||
}
|
||||
nginxFilePath := GetSitePath(*website, SiteConf)
|
||||
if fileOp.Stat(nginxFilePath) {
|
||||
_ = fileOp.DeleteFile(nginxFilePath)
|
||||
if website.Type == constant.Stream {
|
||||
steamFilePath := GetSitePath(*website, StreamConf)
|
||||
if fileOp.Stat(steamFilePath) {
|
||||
_ = fileOp.DeleteFile(steamFilePath)
|
||||
}
|
||||
} else {
|
||||
nginxFilePath := GetSitePath(*website, SiteConf)
|
||||
if fileOp.Stat(nginxFilePath) {
|
||||
_ = fileOp.DeleteFile(nginxFilePath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1089,27 +1140,8 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
|
||||
addPorts = append(addPorts, port)
|
||||
continue
|
||||
}
|
||||
if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 {
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
if appInstall.ID > 0 {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_APP")
|
||||
errMap["name"] = appInstall.Name
|
||||
err = buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
return
|
||||
}
|
||||
runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port))
|
||||
if runtime != nil {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME")
|
||||
errMap["name"] = runtime.Name
|
||||
err = buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
return
|
||||
}
|
||||
if port != defaultHTTPsPort && common.ScanPort(port) {
|
||||
err = buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
return
|
||||
}
|
||||
if err = checkWebsitePort(defaultHTTPsPort, port, ""); err != nil {
|
||||
return
|
||||
}
|
||||
if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID), websiteDomainRepo.WithPort(port)); len(existPorts) == 0 {
|
||||
addPorts = append(addPorts, port)
|
||||
@@ -1119,6 +1151,43 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
|
||||
return
|
||||
}
|
||||
|
||||
func checkWebsitePort(defaultHTTPsPort, port int, websiteType string) error {
|
||||
if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) > 0 {
|
||||
return nil
|
||||
}
|
||||
if websiteType == constant.Stream {
|
||||
websites, _ := websiteRepo.List(websiteRepo.WithType(constant.Stream))
|
||||
for _, website := range websites {
|
||||
ports := strings.Split(website.StreamPorts, ",")
|
||||
for _, p := range ports {
|
||||
pInt, _ := strconv.Atoi(p)
|
||||
if pInt == port {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
if appInstall.ID > 0 {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_APP")
|
||||
errMap["name"] = appInstall.Name
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port))
|
||||
if runtime != nil {
|
||||
errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME")
|
||||
errMap["name"] = runtime.Name
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
if port != defaultHTTPsPort && common.ScanPort(port) {
|
||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
|
||||
if websiteSSL.PushDir {
|
||||
fileOp := files.NewFileOp()
|
||||
@@ -1304,12 +1373,23 @@ const (
|
||||
SitePathAuthBasicDir = "SitePathAuthBasicDir"
|
||||
SiteUpstreamDir = "SiteUpstreamDir"
|
||||
SiteCorsPath = "SiteCorsPath"
|
||||
StreamDir = "StreamDir"
|
||||
StreamConf = "StreamConf"
|
||||
)
|
||||
|
||||
func GetWebsiteConfigPath(website model.Website) string {
|
||||
if website.Type != constant.Stream {
|
||||
return GetSitePath(website, SiteConf)
|
||||
}
|
||||
return GetSitePath(website, StreamConf)
|
||||
}
|
||||
|
||||
func GetSitePath(website model.Website, confType string) string {
|
||||
switch confType {
|
||||
case SiteConf:
|
||||
return path.Join(GetWebSiteRootDir(), "conf.d", website.Alias+".conf")
|
||||
case StreamConf:
|
||||
return path.Join(GetWebSiteRootDir(), "stream.d", website.Alias+".conf")
|
||||
case SiteAccessLog:
|
||||
return path.Join(GteSiteDir(website.Alias), "log", "access.log")
|
||||
case SiteErrorLog:
|
||||
@@ -1346,6 +1426,8 @@ func GetOpenrestyDir(confType string) string {
|
||||
return GetWebSiteRootDir()
|
||||
case SiteConfDir:
|
||||
return path.Join(GetWebSiteRootDir(), "conf.d")
|
||||
case StreamDir:
|
||||
return path.Join(GetWebSiteRootDir(), "steam.d")
|
||||
case SitesRootDir:
|
||||
return path.Join(GetWebSiteRootDir(), "sites")
|
||||
case DefaultDir:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
server {
|
||||
proxy_pass mysql_cluster;
|
||||
access_log /www/sites/domain/log/access.log streamlog;
|
||||
error_log /www/sites/domain/log/error.log;
|
||||
}
|
||||
@@ -4,8 +4,9 @@ const (
|
||||
WebRunning = "Running"
|
||||
WebStopped = "Stopped"
|
||||
|
||||
ProtocolHTTP = "HTTP"
|
||||
ProtocolHTTPS = "HTTPS"
|
||||
ProtocolHTTP = "HTTP"
|
||||
ProtocolHTTPS = "HTTPS"
|
||||
ProtocolStream = "TCP/UDP"
|
||||
|
||||
NewApp = "new"
|
||||
InstalledApp = "installed"
|
||||
@@ -15,6 +16,7 @@ const (
|
||||
Proxy = "proxy"
|
||||
Runtime = "runtime"
|
||||
Subsite = "subsite"
|
||||
Stream = "stream"
|
||||
|
||||
SSLExisted = "existed"
|
||||
SSLAuto = "auto"
|
||||
|
||||
+5
-5
@@ -42,7 +42,7 @@ require (
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/qiniu/go-sdk/v7 v7.21.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.7
|
||||
github.com/shirou/gopsutil/v4 v4.25.11
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
@@ -121,7 +121,7 @@ require (
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
@@ -206,7 +206,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
@@ -221,8 +221,8 @@ require (
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
||||
|
||||
@@ -310,6 +310,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -831,6 +833,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
@@ -896,6 +900,8 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh
|
||||
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -962,8 +968,12 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 h1:ZCmSnT6CLGhfoQ2lPEhL4nsJstKDCw1F1RfN8/smTCU=
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19/go.mod h1:SXTY+QvI+KTTKXQdg0zZ7nx0u94QWh8ZAwBQYsW9cqk=
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
|
||||
|
||||
@@ -57,6 +57,7 @@ func InitAgentDB() {
|
||||
migrations.AddGPUMonitor,
|
||||
migrations.UpdateDatabaseMysql,
|
||||
migrations.InitIptablesStatus,
|
||||
migrations.UpdateWebsite,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -775,3 +775,10 @@ var InitIptablesStatus = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateWebsite = &gormigrate.Migration{
|
||||
ID: "20251203-update-website",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.Website{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -93,5 +93,6 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
websiteRouter.POST("/crosssite", baseApi.OperateCrossSiteAccess)
|
||||
|
||||
websiteRouter.POST("/exec/composer", baseApi.ExecComposer)
|
||||
websiteRouter.POST("/stream/update", baseApi.UpdateStreamConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export namespace Website {
|
||||
dbID: number;
|
||||
dbType: string;
|
||||
favorite: boolean;
|
||||
streamPorts: string;
|
||||
}
|
||||
|
||||
export interface WebsiteDTO extends Website {
|
||||
@@ -37,7 +38,17 @@ export namespace Website {
|
||||
runtimeName: string;
|
||||
runtimeType: string;
|
||||
openBaseDir: boolean;
|
||||
algorithm: string;
|
||||
servers: NginxUpstreamServer[];
|
||||
}
|
||||
|
||||
export interface WebsiteStreamUpdate {
|
||||
websiteID: number;
|
||||
algorithm: string;
|
||||
streamPorts?: string;
|
||||
servers: NginxUpstreamServer[];
|
||||
}
|
||||
|
||||
export interface WebsiteRes extends CommonModel {
|
||||
protocol: string;
|
||||
primaryDomain: string;
|
||||
@@ -93,6 +104,10 @@ export namespace Website {
|
||||
dbUser?: string;
|
||||
dbHost?: string;
|
||||
domains: SubDomain[];
|
||||
streamPorts?: string;
|
||||
name: string;
|
||||
algorithm: string;
|
||||
servers: NginxUpstreamServer[];
|
||||
}
|
||||
|
||||
export interface WebSiteUpdateReq {
|
||||
|
||||
@@ -371,3 +371,7 @@ export const updateCorsConfig = (req: Website.CorsConfigReq) => {
|
||||
export const batchSetGroup = (req: Website.BatchSetGroup) => {
|
||||
return http.post(`/websites/batch/group`, req);
|
||||
};
|
||||
|
||||
export const updateWebsiteStream = (req: Website.WebsiteStreamUpdate) => {
|
||||
return http.post(`/websites/stream/update`, req);
|
||||
};
|
||||
|
||||
@@ -412,23 +412,31 @@ export const Actions = [
|
||||
},
|
||||
];
|
||||
|
||||
export const getAlgorithms = () => [
|
||||
{
|
||||
label: i18n.global.t('commons.table.default'),
|
||||
value: 'default',
|
||||
placeHolder: i18n.global.t('website.defaultHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.ipHash'),
|
||||
value: 'ip_hash',
|
||||
placeHolder: i18n.global.t('website.ipHashHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.leastConn'),
|
||||
value: 'least_conn',
|
||||
placeHolder: i18n.global.t('website.leastConnHelper'),
|
||||
},
|
||||
];
|
||||
export const getAlgorithms = (type: string) => {
|
||||
const baseAlgorithms = [
|
||||
{
|
||||
label: i18n.global.t('commons.table.default'),
|
||||
value: 'default',
|
||||
placeHolder: i18n.global.t('website.defaultHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.ipHash'),
|
||||
value: 'ip_hash',
|
||||
placeHolder: i18n.global.t('website.ipHashHelper'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.leastConn'),
|
||||
value: 'least_conn',
|
||||
placeHolder: i18n.global.t('website.leastConnHelper'),
|
||||
},
|
||||
];
|
||||
|
||||
if (type === 'stream') {
|
||||
return baseAlgorithms.filter((algo) => algo.value !== 'ip_hash');
|
||||
}
|
||||
|
||||
return baseAlgorithms;
|
||||
};
|
||||
|
||||
export const getStatusStrategy = () => [
|
||||
{
|
||||
@@ -462,4 +470,8 @@ export const getWebsiteTypes = () => [
|
||||
label: i18n.global.t('website.subsite'),
|
||||
value: 'subsite',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('website.stream'),
|
||||
value: 'stream',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2699,6 +2699,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Batch Operation',
|
||||
batchOpreateHelper: 'Batch {0} websites, continue operation?',
|
||||
stream: 'TCP/UDP Proxy',
|
||||
streamPorts: 'Listening Ports',
|
||||
streamPortsHelper: 'Separate with commas, e.g., 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Short tag support',
|
||||
|
||||
@@ -2683,6 +2683,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Operación en Lote',
|
||||
batchOpreateHelper: 'Lote {0} sitios web, ¿continuar operación?',
|
||||
stream: 'Proxy TCP/UDP',
|
||||
streamPorts: 'Puertos de escucha',
|
||||
streamPortsHelper: 'Separe con comas, por ejemplo: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Soporte de etiquetas cortas',
|
||||
|
||||
@@ -2622,6 +2622,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'バッチ操作',
|
||||
batchOpreateHelper: 'ウェブサイトをバッチ{0}しますか?',
|
||||
stream: 'TCP/UDP プロキシ',
|
||||
streamPorts: '待ち受けポート',
|
||||
streamPortsHelper: 'カンマで区切ってください(例:3306, 3307)',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短いタグサポート',
|
||||
|
||||
@@ -2576,6 +2576,9 @@ const message = {
|
||||
|
||||
batchOpreate: '일괄 작업',
|
||||
batchOpreateHelper: '웹사이트를 일괄 {0}, 계속 작업하시겠습니까?',
|
||||
stream: 'TCP/UDP 프록시',
|
||||
streamPorts: '수신 포트',
|
||||
streamPortsHelper: '쉼표로 구분하세요. 예: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '짧은 태그 지원',
|
||||
|
||||
@@ -2677,6 +2677,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Operasi Pukal',
|
||||
batchOpreateHelper: 'Pukal {0} laman web, teruskan operasi?',
|
||||
stream: 'Proksi TCP/UDP',
|
||||
streamPorts: 'Port Mendengar',
|
||||
streamPortsHelper: 'Sila asingkan dengan koma, contoh: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Sokongan tag pendek',
|
||||
|
||||
@@ -2682,6 +2682,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Operação em Lote',
|
||||
batchOpreateHelper: 'Lote {0} sites, continuar operação?',
|
||||
stream: 'Proxy TCP/UDP',
|
||||
streamPorts: 'Portas de escuta',
|
||||
streamPortsHelper: 'Separe por vírgulas, por exemplo: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Suporte para short tags',
|
||||
|
||||
@@ -2678,6 +2678,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Пакетная операция',
|
||||
batchOpreateHelper: 'Пакетное {0} веб-сайтов, продолжить операцию?',
|
||||
stream: 'Прокси TCP/UDP',
|
||||
streamPorts: 'Порты прослушивания',
|
||||
streamPortsHelper: 'Разделяйте запятыми, например: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Поддержка коротких тегов',
|
||||
|
||||
@@ -2735,6 +2735,9 @@ const message = {
|
||||
|
||||
batchOpreate: 'Toplu İşlem',
|
||||
batchOpreateHelper: 'Toplu {0} web siteleri, işlemi devam ettir?',
|
||||
stream: 'TCP/UDP Proxy',
|
||||
streamPorts: 'Dinleme Portları',
|
||||
streamPortsHelper: 'Virgülle ayırın, örneğin: 3306, 3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Kısa etiket desteği',
|
||||
|
||||
@@ -2510,6 +2510,9 @@ const message = {
|
||||
|
||||
batchOpreate: '批次操作',
|
||||
batchOpreateHelper: '批次{0}網站,是否繼續操作?',
|
||||
stream: 'TCP/UDP 代理',
|
||||
streamPorts: '監聽端口',
|
||||
streamPortsHelper: '請用逗號分隔,例如:3306,3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短標籤支援',
|
||||
|
||||
@@ -2506,6 +2506,9 @@ const message = {
|
||||
|
||||
batchOpreate: '批量操作',
|
||||
batchOpreateHelper: '批量{0}网站,是否继续操作?',
|
||||
stream: 'TCP/UDP 代理',
|
||||
streamPorts: '监听端口',
|
||||
streamPortsHelper: '请按逗号分割,例如:3306,3307',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短标签支持',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-descriptions :column="7" border direction="vertical">
|
||||
<el-descriptions-item :label="$t('website.primaryDomain')">
|
||||
{{ websiteSSL.primaryDomain }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.otherDomains')">
|
||||
{{ websiteSSL.domains }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.brand')">
|
||||
{{ websiteSSL.organization }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ssl.provider')">
|
||||
{{ getProvider(websiteSSL.provider) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
:label="$t('ssl.acmeAccount')"
|
||||
v-if="websiteSSL.acmeAccount && websiteSSL.provider !== 'manual'"
|
||||
>
|
||||
{{ websiteSSL.acmeAccount.email }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.expireDate')">
|
||||
{{ dateFormatSimple(websiteSSL.expireDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.remark')">
|
||||
{{ websiteSSL.description }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatSimple, getProvider } from '@/utils/util';
|
||||
defineProps({
|
||||
websiteSSL: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -133,32 +133,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0">
|
||||
<el-descriptions :column="7" border direction="vertical">
|
||||
<el-descriptions-item :label="$t('website.primaryDomain')">
|
||||
{{ websiteSSL.primaryDomain }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.otherDomains')">
|
||||
{{ websiteSSL.domains }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.brand')">
|
||||
{{ websiteSSL.organization }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ssl.provider')">
|
||||
{{ getProvider(websiteSSL.provider) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
:label="$t('ssl.acmeAccount')"
|
||||
v-if="websiteSSL.acmeAccount && websiteSSL.provider !== 'manual'"
|
||||
>
|
||||
{{ websiteSSL.acmeAccount.email }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.expireDate')">
|
||||
{{ dateFormatSimple(websiteSSL.expireDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.remark')">
|
||||
{{ websiteSSL.description }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<WebsiteSSL :websiteSSL="websiteSSL" />
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">{{ $t('website.SSLProConfig') }}</el-divider>
|
||||
<el-form-item :label="$t('website.supportProtocol')" prop="SSLProtocol">
|
||||
@@ -191,15 +166,17 @@
|
||||
<FileList ref="certFileRef" @choose="getCertificatePath" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import WebsiteSSL from '@/views/website/website/components/website-ssl/index.vue';
|
||||
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { getHTTPSConfig, listSSL, searchAcmeAccount, updateHTTPSConfig } from '@/api/modules/website';
|
||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
|
||||
const keyFileRef = ref();
|
||||
const certFileRef = ref();
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div :style="{ '--main-height': mainHeight + 'px' }">
|
||||
<el-tabs tab-position="left" v-model="tabIndex" v-if="id > 0" class="custom-tabs" ref="tabsRef">
|
||||
<el-tabs
|
||||
tab-position="left"
|
||||
v-model="tabIndex"
|
||||
v-if="id > 0 && website.type != 'stream'"
|
||||
class="custom-tabs"
|
||||
ref="tabsRef"
|
||||
>
|
||||
<el-tab-pane :label="$t('website.domainConfig')" name="0">
|
||||
<Domain :key="id" :id="id" v-if="tabIndex == '0'"></Domain>
|
||||
</el-tab-pane>
|
||||
@@ -59,6 +65,20 @@
|
||||
<Other :id="id" v-if="tabIndex == '12'"></Other>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-tabs
|
||||
tab-position="left"
|
||||
v-model="tabIndex"
|
||||
v-if="id > 0 && website.type == 'stream'"
|
||||
class="custom-tabs"
|
||||
ref="tabsRef"
|
||||
>
|
||||
<el-tab-pane :label="$t('website.other')" name="12">
|
||||
<Other :id="id" v-if="tabIndex == '12'"></Other>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.stream')" name="13">
|
||||
<Stream :id="id" v-if="tabIndex == '13'"></Stream>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -81,6 +101,7 @@ import PHP from './php/index.vue';
|
||||
import RealIP from './real-ip/index.vue';
|
||||
import Resource from './resource/index.vue';
|
||||
import Cors from './cors/index.vue';
|
||||
import Stream from './stream/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
website: {
|
||||
@@ -98,9 +119,10 @@ const id = computed(() => {
|
||||
return props.website.id;
|
||||
});
|
||||
const tabIndex = ref('0');
|
||||
const menuKey = ref('site-tabIndex-');
|
||||
|
||||
watch(tabIndex, (newVal) => {
|
||||
localStorage.setItem('site-tabIndex', newVal);
|
||||
localStorage.setItem(menuKey.value, newVal);
|
||||
});
|
||||
|
||||
const handleResize = () => {
|
||||
@@ -123,9 +145,15 @@ const handleScroll = (event: WheelEvent) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const storedTabIndex = localStorage.getItem('site-tabIndex');
|
||||
menuKey.value = 'site-tabIndex-' + props.website.id;
|
||||
const storedTabIndex = localStorage.getItem(menuKey.value);
|
||||
if (storedTabIndex !== null) {
|
||||
tabIndex.value = storedTabIndex;
|
||||
} else {
|
||||
tabIndex.value = '0';
|
||||
if (props.website.type == 'stream') {
|
||||
tabIndex.value = '12';
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', handleResize);
|
||||
document.addEventListener('wheel', handleScroll, { passive: false });
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<el-form ref="lbFormRef" label-position="top" :model="formData" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="formData.name" :disabled="disabled"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.algorithm')" prop="algorithm">
|
||||
<el-select v-model="formData.algorithm" @change="handleAlgorithmChange">
|
||||
<el-option
|
||||
v-for="(algorithm, index) in Algorithms"
|
||||
:label="algorithm.label"
|
||||
:key="index"
|
||||
:value="algorithm.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help">{{ getHelper(formData.algorithm) }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<div>
|
||||
<el-card v-for="(server, index) of formData.servers" :key="index" class="server-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="server-title">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
<span class="ml-2">{{ $t('website.server') }} - {{ server.server || index + 1 }}</span>
|
||||
</span>
|
||||
<el-button
|
||||
v-if="formData.servers.length > 1"
|
||||
text
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
size="small"
|
||||
@click="removeServer(index)"
|
||||
></el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('setting.address')"
|
||||
:prop="`servers.${index}.server`"
|
||||
:rules="rules.server"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.servers[index].server"
|
||||
:placeholder="'example.com:8080'"
|
||||
@input="handleChange"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.weight')"
|
||||
:prop="`servers.${index}.weight`"
|
||||
:rules="rules.weight"
|
||||
>
|
||||
<el-input
|
||||
type="number"
|
||||
v-model.number="formData.servers[index].weight"
|
||||
@input="handleChange"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.strategy')"
|
||||
:prop="`servers.${index}.flag`"
|
||||
:rules="rules.flag"
|
||||
>
|
||||
<el-select v-model="formData.servers[index].flag" clearable @change="handleChange">
|
||||
<el-option
|
||||
v-for="flag in getStatusStrategy()"
|
||||
:label="flag.label"
|
||||
:key="flag.value"
|
||||
:value="flag.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.maxFails')"
|
||||
:prop="`servers.${index}.maxFails`"
|
||||
:rules="rules.maxFails"
|
||||
>
|
||||
<el-input
|
||||
type="number"
|
||||
v-model.number="formData.servers[index].maxFails"
|
||||
@input="handleChange"
|
||||
>
|
||||
<template #append>{{ $t('commons.units.time') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item :prop="`servers.${index}.failTimeout`" :rules="rules.failTimeout">
|
||||
<template #label>
|
||||
<span class="inline-flex items-center">
|
||||
{{ $t('website.failTimeout') }}
|
||||
<el-tooltip :content="$t('website.failTimeoutHelper')" placement="top">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input
|
||||
type="number"
|
||||
v-model.number="formData.servers[index].failTimeout"
|
||||
@input="handleChange"
|
||||
>
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model.number="formData.servers[index].failTimeoutUnit"
|
||||
class="!w-24"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(unit, indexKey) in Units"
|
||||
:key="indexKey"
|
||||
:label="unit.label"
|
||||
:value="unit.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.maxConns')"
|
||||
:prop="`servers.${index}.maxConns`"
|
||||
:rules="rules.maxConns"
|
||||
>
|
||||
<el-input
|
||||
type="number"
|
||||
v-model.number="formData.servers[index].maxConns"
|
||||
@input="handleChange"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-button class="add-server-btn" type="primary" plain @click="addServer" icon="Plus">
|
||||
{{ $t('commons.button.add') + $t('website.server') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { getAlgorithms, getStatusStrategy, Units } from '@/global/mimetype';
|
||||
import { Monitor, QuestionFilled } from '@element-plus/icons-vue';
|
||||
|
||||
interface Server {
|
||||
server: string;
|
||||
weight?: number;
|
||||
maxFails?: number;
|
||||
maxConns?: number;
|
||||
failTimeout?: number;
|
||||
failTimeoutUnit: string;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
interface LoadBalanceFormData {
|
||||
name: string;
|
||||
algorithm: string;
|
||||
servers: Server[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: LoadBalanceFormData;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: LoadBalanceFormData): void;
|
||||
}>();
|
||||
|
||||
const lbFormRef = ref<FormInstance>();
|
||||
|
||||
const formData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const rules = ref<any>({
|
||||
name: [Rules.appName],
|
||||
algorithm: [Rules.requiredSelect],
|
||||
server: [Rules.requiredInput],
|
||||
weight: [checkNumberRange(0, 100)],
|
||||
servers: {
|
||||
type: Array,
|
||||
},
|
||||
maxFails: [checkNumberRange(0, 1000)],
|
||||
maxConns: [checkNumberRange(0, 10000)],
|
||||
failTimeout: [checkNumberRange(0, 300)],
|
||||
});
|
||||
|
||||
const Algorithms = getAlgorithms('stream');
|
||||
|
||||
const helper = ref();
|
||||
const getHelper = (key: string) => {
|
||||
Algorithms.forEach((algorithm) => {
|
||||
if (algorithm.value === key) {
|
||||
helper.value = algorithm.placeHolder;
|
||||
}
|
||||
});
|
||||
return helper.value;
|
||||
};
|
||||
|
||||
const initServer = (): Server => ({
|
||||
server: '',
|
||||
weight: undefined,
|
||||
maxFails: undefined,
|
||||
maxConns: undefined,
|
||||
failTimeout: undefined,
|
||||
failTimeoutUnit: 's',
|
||||
flag: '',
|
||||
});
|
||||
|
||||
const addServer = () => {
|
||||
const newServers = [...formData.value.servers, initServer()];
|
||||
emit('update:modelValue', { ...formData.value, servers: newServers });
|
||||
};
|
||||
|
||||
const removeServer = (index: number) => {
|
||||
const newServers = [...formData.value.servers];
|
||||
newServers.splice(index, 1);
|
||||
emit('update:modelValue', { ...formData.value, servers: newServers });
|
||||
};
|
||||
|
||||
const handleChange = () => {
|
||||
emit('update:modelValue', { ...formData.value });
|
||||
};
|
||||
|
||||
const handleAlgorithmChange = () => {
|
||||
handleChange();
|
||||
};
|
||||
|
||||
const validate = async () => {
|
||||
if (!lbFormRef.value) return false;
|
||||
return await lbFormRef.value.validate();
|
||||
};
|
||||
|
||||
const resetFields = () => {
|
||||
lbFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const clearValidate = () => {
|
||||
lbFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
resetFields,
|
||||
clearValidate,
|
||||
formRef: lbFormRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.server-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 12px 20px;
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
|
||||
.server-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.add-server-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border-style: dashed;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -90,7 +90,7 @@ const loading = ref(false);
|
||||
const operateRef = ref();
|
||||
const delRef = ref();
|
||||
const fileRef = ref();
|
||||
const Algorithms = getAlgorithms();
|
||||
const Algorithms = getAlgorithms('');
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
||||
@@ -3,149 +3,17 @@
|
||||
v-model="open"
|
||||
@close="handleClose"
|
||||
size="large"
|
||||
:header="$t('commons.button.' + item.operate) + $t('website.loadBalance')"
|
||||
:resource="item.operate == 'create' ? '' : item.name"
|
||||
:header="$t('commons.button.' + operate) + $t('website.loadBalance')"
|
||||
:resource="operate == 'create' ? '' : formData.name"
|
||||
>
|
||||
<el-form ref="lbForm" label-position="top" :model="item" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="item.name" :disabled="item.operate === 'edit'"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.algorithm')" prop="algorithm">
|
||||
<el-select v-model="item.algorithm">
|
||||
<el-option
|
||||
v-for="(algorithm, index) in Algorithms"
|
||||
:label="algorithm.label"
|
||||
:key="index"
|
||||
:value="algorithm.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help">{{ getHelper(item.algorithm) }}</span>
|
||||
</el-form-item>
|
||||
<LoadBalanceForm ref="lbFormRef" v-model="formData" :disabled="operate === 'edit' && disableName" />
|
||||
|
||||
<div>
|
||||
<el-card v-for="(server, index) of item.servers" :key="index" class="server-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="server-title">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
<span class="ml-2">{{ $t('website.server') }} - {{ server.server }}</span>
|
||||
</span>
|
||||
<el-button
|
||||
v-if="item.servers.length > 1"
|
||||
text
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
size="small"
|
||||
@click="removeServer(index)"
|
||||
></el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
:label="$t('setting.address')"
|
||||
:prop="`servers.${index}.server`"
|
||||
:rules="rules.server"
|
||||
>
|
||||
<el-input
|
||||
v-model="item.servers[index].server"
|
||||
:placeholder="'example.com:8080'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.weight')"
|
||||
:prop="`servers.${index}.weight`"
|
||||
:rules="rules.weight"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].weight"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.strategy')"
|
||||
:prop="`servers.${index}.flag`"
|
||||
:rules="rules.flag"
|
||||
>
|
||||
<el-select v-model="item.servers[index].flag" clearable>
|
||||
<el-option
|
||||
v-for="flag in getStatusStrategy()"
|
||||
:label="flag.label"
|
||||
:key="flag.value"
|
||||
:value="flag.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.maxFails')"
|
||||
:prop="`servers.${index}.maxFails`"
|
||||
:rules="rules.maxFails"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].maxFails">
|
||||
<template #append>{{ $t('commons.units.time') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item :prop="`servers.${index}.failTimeout`" :rules="rules.failTimeout">
|
||||
<template #label>
|
||||
<span class="inline-flex items-center">
|
||||
{{ $t('website.failTimeout') }}
|
||||
<el-tooltip :content="$t('website.failTimeoutHelper')" placement="top">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input type="number" v-model.number="item.servers[index].failTimeout">
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model.number="item.servers[index].failTimeoutUnit"
|
||||
class="!w-24"
|
||||
>
|
||||
<el-option
|
||||
v-for="(unit, indexKey) in Units"
|
||||
:key="indexKey"
|
||||
:label="unit.label"
|
||||
:value="unit.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-form-item
|
||||
:label="$t('website.maxConns')"
|
||||
:prop="`servers.${index}.maxConns`"
|
||||
:rules="rules.maxConns"
|
||||
>
|
||||
<el-input type="number" v-model.number="item.servers[index].maxConns"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-button class="add-server-btn" type="primary" plain @click="addServer" icon="Plus">
|
||||
{{ $t('commons.button.add') + $t('website.server') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(lbForm)" :disabled="loading">
|
||||
<el-button @click="handleClose" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -154,29 +22,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { createLoadBalance, updateLoadBalance } from '@/api/modules/website';
|
||||
import i18n from '@/lang';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { getAlgorithms, getStatusStrategy } from '@/global/mimetype';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { Monitor, QuestionFilled } from '@element-plus/icons-vue';
|
||||
import { Units } from '@/global/mimetype';
|
||||
|
||||
const rules = ref<any>({
|
||||
name: [Rules.appName],
|
||||
algorithm: [Rules.requiredSelect],
|
||||
server: [Rules.requiredInput],
|
||||
weight: [checkNumberRange(0, 100)],
|
||||
servers: {
|
||||
type: Array,
|
||||
},
|
||||
maxFails: [checkNumberRange(1, 1000)],
|
||||
maxConns: [checkNumberRange(0, 10000)],
|
||||
failTimeout: [checkNumberRange(1, 300)],
|
||||
});
|
||||
import LoadBalanceForm from '@/views/website/website/config/basic/load-balance/form/index.vue';
|
||||
|
||||
interface LoadBalanceOperate {
|
||||
websiteID: number;
|
||||
@@ -184,62 +35,46 @@ interface LoadBalanceOperate {
|
||||
upstream?: Website.NginxUpstream;
|
||||
}
|
||||
|
||||
const lbForm = ref<FormInstance>();
|
||||
|
||||
const initServer = () => ({
|
||||
server: '',
|
||||
weight: undefined,
|
||||
maxFails: undefined,
|
||||
maxConns: undefined,
|
||||
failTimeout: undefined,
|
||||
failTimeoutUnit: 's',
|
||||
flag: '',
|
||||
});
|
||||
|
||||
const lbFormRef = ref();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const item = ref({
|
||||
websiteID: 0,
|
||||
const operate = ref('create');
|
||||
const websiteID = ref(0);
|
||||
const disableName = ref(false);
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
operate: 'create',
|
||||
servers: [],
|
||||
algorithm: 'default',
|
||||
flag: '',
|
||||
servers: [
|
||||
{
|
||||
server: '',
|
||||
weight: undefined,
|
||||
maxFails: undefined,
|
||||
maxConns: undefined,
|
||||
failTimeout: undefined,
|
||||
failTimeoutUnit: 's',
|
||||
flag: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const handleClose = () => {
|
||||
lbForm.value?.resetFields();
|
||||
lbFormRef.value?.resetFields();
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const helper = ref();
|
||||
const Algorithms = getAlgorithms();
|
||||
const getHelper = (key: string) => {
|
||||
Algorithms.forEach((algorithm) => {
|
||||
if (algorithm.value === key) {
|
||||
helper.value = algorithm.placeHolder;
|
||||
}
|
||||
});
|
||||
return helper.value;
|
||||
};
|
||||
|
||||
const addServer = () => {
|
||||
item.value.servers.push(initServer());
|
||||
};
|
||||
|
||||
const removeServer = (index: number) => {
|
||||
item.value.servers.splice(index, 1);
|
||||
};
|
||||
|
||||
const acceptParams = async (req: LoadBalanceOperate) => {
|
||||
item.value.websiteID = req.websiteID;
|
||||
item.value.operate = req.operate;
|
||||
websiteID.value = req.websiteID;
|
||||
operate.value = req.operate;
|
||||
|
||||
if (req.operate == 'edit') {
|
||||
item.value.operate = 'edit';
|
||||
item.value.name = req.upstream?.name || '';
|
||||
item.value.algorithm = req.upstream?.algorithm || 'default';
|
||||
disableName.value = true;
|
||||
formData.value.name = req.upstream?.name || '';
|
||||
formData.value.algorithm = req.upstream?.algorithm || 'default';
|
||||
|
||||
let servers = [];
|
||||
req.upstream?.servers?.forEach((server) => {
|
||||
const weight = server.weight == 0 ? undefined : server.weight;
|
||||
@@ -257,16 +92,28 @@ const acceptParams = async (req: LoadBalanceOperate) => {
|
||||
flag: server.flag,
|
||||
});
|
||||
});
|
||||
item.value.servers = servers;
|
||||
formData.value.servers = servers;
|
||||
} else {
|
||||
item.value.name = '';
|
||||
item.value.servers = [initServer()];
|
||||
disableName.value = false;
|
||||
formData.value.name = '';
|
||||
formData.value.algorithm = 'default';
|
||||
formData.value.servers = [
|
||||
{
|
||||
server: '',
|
||||
weight: undefined,
|
||||
maxFails: undefined,
|
||||
maxConns: undefined,
|
||||
failTimeout: undefined,
|
||||
failTimeoutUnit: 's',
|
||||
flag: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const handleServers = () => {
|
||||
for (const server of item.value.servers) {
|
||||
for (const server of formData.value.servers) {
|
||||
if (!server.weight || server.weight == '') {
|
||||
server.weight = 0;
|
||||
}
|
||||
@@ -283,7 +130,7 @@ const handleServers = () => {
|
||||
};
|
||||
|
||||
const rollBackServers = () => {
|
||||
for (const server of item.value.servers) {
|
||||
for (const server of formData.value.servers) {
|
||||
if (server.weight == 0) {
|
||||
server.weight = undefined;
|
||||
}
|
||||
@@ -299,87 +146,44 @@ const rollBackServers = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let checkBackup = false;
|
||||
if (item.value.algorithm == 'ip_hash') {
|
||||
checkBackup = true;
|
||||
}
|
||||
for (const server of item.value.servers) {
|
||||
if (checkBackup && server.flag == 'backup') {
|
||||
MsgError(i18n.global.t('website.ipHashBackupErr'));
|
||||
return;
|
||||
const submit = async () => {
|
||||
try {
|
||||
const valid = await lbFormRef.value?.validate();
|
||||
if (!valid) return;
|
||||
|
||||
if (formData.value.algorithm == 'ip_hash') {
|
||||
for (const server of formData.value.servers) {
|
||||
if (server.flag == 'backup') {
|
||||
MsgError(i18n.global.t('website.ipHashBackupErr'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleServers();
|
||||
loading.value = true;
|
||||
try {
|
||||
if (item.value.operate === 'edit') {
|
||||
await updateLoadBalance(item.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
} else {
|
||||
await createLoadBalance(item.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
}
|
||||
handleClose();
|
||||
} catch {
|
||||
rollBackServers();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
const submitData = {
|
||||
websiteID: websiteID.value,
|
||||
...formData.value,
|
||||
};
|
||||
|
||||
if (operate.value === 'edit') {
|
||||
await updateLoadBalance(submitData);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
} else {
|
||||
await createLoadBalance(submitData);
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
}
|
||||
});
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
rollBackServers();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.server-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 12px 20px;
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
|
||||
.server-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.add-server-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border-style: dashed;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form ref="streamFormRef" :model="form" label-width="120px" label-position="top">
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="12" :offset="1">
|
||||
<el-form-item :label="$t('website.streamPorts')" :rules="Rules.requiredInput">
|
||||
<el-input v-model="form.streamPorts" />
|
||||
</el-form-item>
|
||||
<LoadBalanceForm ref="lbFormRef" v-model="form" :disabled="true" />
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit()" class="mt-2">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LoadBalanceForm from '@/views/website/website/config/basic/load-balance/form/index.vue';
|
||||
|
||||
import { getWebsite, updateWebsiteStream } from '@/api/modules/website';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const form = ref({
|
||||
streamPorts: '',
|
||||
servers: [],
|
||||
name: '',
|
||||
algorithm: '',
|
||||
websiteID: props.id,
|
||||
});
|
||||
const streamFormRef = ref();
|
||||
const lbFormRef = ref();
|
||||
const loading = ref(false);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const formValid = await streamFormRef.value?.validate();
|
||||
if (!formValid) return;
|
||||
const lbValid = await lbFormRef.value?.validate();
|
||||
if (!lbValid) return;
|
||||
await updateWebsiteStream(form.value);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
} catch (error) {
|
||||
if (error.message != '') {
|
||||
MsgError(error.message);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await getWebsite(props.id);
|
||||
form.value.streamPorts = res.data.streamPorts;
|
||||
form.value.servers = res.data.servers;
|
||||
form.value.name = res.data.primaryDomain;
|
||||
form.value.algorithm = res.data.algorithm == '' ? 'default' : res.data.algorithm;
|
||||
form.value.streamPorts = res.data.streamPorts;
|
||||
});
|
||||
</script>
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
<template #main>
|
||||
<MainDiv :heightDiff="260">
|
||||
<Basic :website="website" v-if="index === 'basic'" :heightDiff="320"></Basic>
|
||||
<Basic :website="website" v-if="index === 'basic' && website.id" :heightDiff="320"></Basic>
|
||||
<Log :id="id" v-if="index === 'log'"></Log>
|
||||
<Resource :id="id" v-if="index === 'resource'"></Resource>
|
||||
</MainDiv>
|
||||
@@ -77,22 +77,18 @@ const changeTab = (index: string) => {
|
||||
routerToNameWithParams('WebsiteConfig', { id: id.value, tab: index });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
index.value = props.tab;
|
||||
id.value = Number(props.id);
|
||||
loading.value = true;
|
||||
getWebsite(id.value)
|
||||
.then(async (res) => {
|
||||
website.value = res.data;
|
||||
if (res.data.type === 'runtime') {
|
||||
const runRes = await GetRuntime(res.data.runtimeID);
|
||||
if (runRes.data.type == 'php' && runRes.data.resource === 'appstore') {
|
||||
configPHP.value = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
const res = await getWebsite(id.value);
|
||||
website.value = res.data;
|
||||
if (res.data.type === 'runtime') {
|
||||
const runRes = await GetRuntime(res.data.runtimeID);
|
||||
if (runRes.data.type == 'php' && runRes.data.resource === 'appstore') {
|
||||
configPHP.value = true;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -185,7 +185,17 @@
|
||||
{{ $t('website.runtimePortWarn') }}
|
||||
</el-text>
|
||||
</div>
|
||||
<DomainCreate v-model:form="website" @gengerate="websiteForm.clearValidate()"></DomainCreate>
|
||||
<div v-if="website.type === 'stream'">
|
||||
<el-form-item :label="$t('website.streamPorts')" prop="streamPorts">
|
||||
<el-input
|
||||
v-model="website.streamPorts"
|
||||
:placeholder="$t('website.streamPortsHelper')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-else>
|
||||
<DomainCreate v-model:form="website" @gengerate="websiteForm.clearValidate()"></DomainCreate>
|
||||
</div>
|
||||
<el-form-item prop="IPV6">
|
||||
<el-checkbox v-model="website.IPV6" :label="$t('website.ipv6')" size="large" />
|
||||
</el-form-item>
|
||||
@@ -323,76 +333,54 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item prop="enableSSL">
|
||||
<el-checkbox v-model="website.enableSSL" :label="$t('website.enableHTTPS')" size="large" />
|
||||
<span class="input-help">{{ $t('website.enableSSLHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="website.enableSSL">
|
||||
<el-form-item :label="$t('website.acmeAccountManage')" prop="acmeAccountID">
|
||||
<el-select
|
||||
v-model="website.acmeAccountID"
|
||||
:placeholder="$t('website.selectAcme')"
|
||||
@change="listSSLs"
|
||||
>
|
||||
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
|
||||
<el-option
|
||||
v-for="(acme, index) in acmeAccounts"
|
||||
:key="index"
|
||||
:label="acme.email"
|
||||
:value="acme.id"
|
||||
<div v-if="website.type == 'stream'">
|
||||
<LoadBalanceForm ref="lbFormRef" v-model="steamConfig" :disabled="true" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-form-item prop="enableSSL">
|
||||
<el-checkbox v-model="website.enableSSL" :label="$t('website.enableHTTPS')" size="large" />
|
||||
<span class="input-help">{{ $t('website.enableSSLHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="website.enableSSL">
|
||||
<el-form-item :label="$t('website.acmeAccountManage')" prop="acmeAccountID">
|
||||
<el-select
|
||||
v-model="website.acmeAccountID"
|
||||
:placeholder="$t('website.selectAcme')"
|
||||
@change="listSSLs"
|
||||
>
|
||||
<span>
|
||||
{{ acme.email }}
|
||||
<el-tag class="ml-5">{{ getAccountName(acme.type) }}</el-tag>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.ssl')" prop="websiteSSLID" :hide-required-asterisk="true">
|
||||
<el-select
|
||||
v-model="website.websiteSSLID"
|
||||
:placeholder="$t('website.selectSSL')"
|
||||
@change="handleSSLSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(ssl, index) in ssls"
|
||||
:key="index"
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
:disabled="ssl.pem == ''"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0">
|
||||
<el-descriptions :column="7" border direction="vertical">
|
||||
<el-descriptions-item :label="$t('website.primaryDomain')">
|
||||
{{ websiteSSL.primaryDomain }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.otherDomains')">
|
||||
{{ websiteSSL.domains }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.brand')">
|
||||
{{ websiteSSL.organization }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('ssl.provider')">
|
||||
{{ getProvider(websiteSSL.provider) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
:label="$t('ssl.acmeAccount')"
|
||||
v-if="websiteSSL.acmeAccount && websiteSSL.provider !== 'manual'"
|
||||
<el-option :key="0" :label="$t('website.imported')" :value="0"></el-option>
|
||||
<el-option
|
||||
v-for="(acme, index) in acmeAccounts"
|
||||
:key="index"
|
||||
:label="acme.email"
|
||||
:value="acme.id"
|
||||
>
|
||||
<span>
|
||||
{{ acme.email }}
|
||||
<el-tag class="ml-5">{{ getAccountName(acme.type) }}</el-tag>
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.ssl')" prop="websiteSSLID" :hide-required-asterisk="true">
|
||||
<el-select
|
||||
v-model="website.websiteSSLID"
|
||||
:placeholder="$t('website.selectSSL')"
|
||||
@change="handleSSLSelectChange"
|
||||
>
|
||||
{{ websiteSSL.acmeAccount.email }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.expireDate')">
|
||||
{{ dateFormatSimple(websiteSSL.expireDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('website.remark')">
|
||||
{{ websiteSSL.description }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form-item>
|
||||
<el-option
|
||||
v-for="(ssl, index) in ssls"
|
||||
:key="index"
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
:disabled="ssl.pem == ''"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0">
|
||||
<WebsiteSSL :websiteSSL="websiteSSL" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item :label="$t('website.remark')" prop="remark">
|
||||
<el-input type="textarea" :rows="3" clearable v-model="website.remark" />
|
||||
@@ -422,6 +410,8 @@ import DomainCreate from '@/views/website/website/domain-create/index.vue';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import Check from '../check/index.vue';
|
||||
import GroupSelect from '@/views/website/website/components/group/index.vue';
|
||||
import WebsiteSSL from '@/views/website/website/components/website-ssl/index.vue';
|
||||
import LoadBalanceForm from '@/views/website/website/config/basic/load-balance/form/index.vue';
|
||||
|
||||
import { App } from '@/api/interface/app';
|
||||
import { searchApp, getAppInstalled } from '@/api/modules/app';
|
||||
@@ -443,7 +433,7 @@ import { Runtime } from '@/api/interface/runtime';
|
||||
import { getRandomStr, getRuntimeLabel } from '@/utils/util';
|
||||
import { getAppService } from '@/api/modules/app';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { dateFormatSimple, getProvider, getAccountName } from '@/utils/util';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { getPathByType } from '@/api/modules/files';
|
||||
import { getWebsiteTypes } from '@/global/mimetype';
|
||||
@@ -509,6 +499,11 @@ const initData = () => ({
|
||||
domains: [],
|
||||
parentWebsiteID: undefined,
|
||||
siteDir: '',
|
||||
|
||||
streamPorts: '',
|
||||
name: '',
|
||||
algorithm: '',
|
||||
servers: [],
|
||||
});
|
||||
const website = ref(initData());
|
||||
const rules = ref<any>({
|
||||
@@ -540,6 +535,7 @@ const rules = ref<any>({
|
||||
websiteSSLID: [Rules.requiredSelect],
|
||||
parentWebsiteID: [Rules.requiredSelect],
|
||||
siteDir: [Rules.requiredSelect],
|
||||
streamPorts: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
@@ -575,6 +571,22 @@ const dirs = ref([]);
|
||||
const runtimePorts = ref([]);
|
||||
const WebsiteTypes = getWebsiteTypes();
|
||||
const installFormRef = ref();
|
||||
const lbFormRef = ref();
|
||||
const steamConfig = ref({
|
||||
name: '',
|
||||
algorithm: 'default',
|
||||
servers: [
|
||||
{
|
||||
server: '',
|
||||
weight: undefined,
|
||||
maxFails: undefined,
|
||||
maxConns: undefined,
|
||||
failTimeout: undefined,
|
||||
failTimeoutUnit: 's',
|
||||
flag: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
@@ -935,6 +947,10 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
MsgError(i18n.global.t('website.runtimePortWarn'));
|
||||
return;
|
||||
}
|
||||
if (website.value.type == 'stream') {
|
||||
const isValid = await lbFormRef.value?.validate();
|
||||
if (!isValid) return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await preCheck({});
|
||||
@@ -948,6 +964,11 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
website.value.ftpUser = '';
|
||||
website.value.ftpPassword = '';
|
||||
}
|
||||
if (website.value.type == 'stream') {
|
||||
website.value.name = steamConfig.value.name;
|
||||
website.value.algorithm = steamConfig.value.algorithm;
|
||||
website.value.servers = steamConfig.value.servers;
|
||||
}
|
||||
const taskID = uuidv4();
|
||||
website.value.taskID = taskID;
|
||||
await createWebsite(website.value);
|
||||
@@ -993,6 +1014,16 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => website.value.alias,
|
||||
(value) => {
|
||||
if (website.value.type === 'stream') {
|
||||
steamConfig.value.name = value;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const changeAlias = (value: string) => {
|
||||
const domain = value.split(':')[0];
|
||||
website.value.alias = domain;
|
||||
|
||||
@@ -13,7 +13,13 @@
|
||||
<el-text v-else type="primary" class="cursor-pointer" @click="openConfig(row.id)">
|
||||
{{ row.primaryDomain }}
|
||||
</el-text>
|
||||
<el-popover placement="right" trigger="hover" :width="300" @before-enter="searchDomains(row.id)">
|
||||
<el-popover
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
:width="300"
|
||||
@before-enter="searchDomains(row.id)"
|
||||
v-if="row.type != 'stream'"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link icon="Promotion" class="ml-2.5"></el-button>
|
||||
</template>
|
||||
@@ -32,7 +38,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</el-popover>
|
||||
<el-button link icon="edit" @click="startEdit" v-if="!isEditing"></el-button>
|
||||
<el-button link icon="edit" class="ml-2.5" @click="startEdit" v-if="!isEditing"></el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip effect="dark" :content="$t('website.cancelFavorite')" placement="top-start" v-if="row.favorite">
|
||||
|
||||
@@ -128,14 +128,9 @@
|
||||
v-if="row.status === 'Running'"
|
||||
:operate="true"
|
||||
:status="row.status"
|
||||
@click="operateWebsite('stop', row.id)"
|
||||
/>
|
||||
<Status
|
||||
v-else
|
||||
:status="row.status"
|
||||
:operate="true"
|
||||
@click="operateWebsite('start', row.id)"
|
||||
@click="operateWebsite('stop', row)"
|
||||
/>
|
||||
<Status v-else :status="row.status" :operate="true" @click="operateWebsite('start', row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -466,6 +461,9 @@ const refreshData = () => {
|
||||
};
|
||||
|
||||
const openDatePicker = (row: any) => {
|
||||
if (row.type === 'stream') {
|
||||
return;
|
||||
}
|
||||
refreshData();
|
||||
row.showdate = true;
|
||||
};
|
||||
@@ -590,12 +588,15 @@ const checkDate = (date: Date) => {
|
||||
return date.getTime() < now.getTime();
|
||||
};
|
||||
|
||||
const operateWebsite = (op: string, id: number) => {
|
||||
const operateWebsite = (op: string, row: Website.Website) => {
|
||||
if (row.type === 'stream') {
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm(i18n.global.t('website.' + op + 'Helper'), i18n.global.t('cronjob.changeStatus'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await opWebsite({ id: id, operate: op });
|
||||
await opWebsite({ id: row.id, operate: op });
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user