diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go index f1a517199cf4..059aaf97f01b 100644 --- a/agent/app/api/v2/website.go +++ b/agent/app/api/v2/website.go @@ -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) +} diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index 88849023752b..bdc8b5b5fcfc 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -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 { diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go index 0f6dcb6bacb8..15aee9650d56 100644 --- a/agent/app/dto/response/website.go +++ b/agent/app/dto/response/website.go @@ -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 { diff --git a/agent/app/model/website.go b/agent/app/model/website.go index 27426c40bbca..10b2b58261f2 100644 --- a/agent/app/model/website.go +++ b/agent/app/model/website.go @@ -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"` } diff --git a/agent/app/service/nginx_utils.go b/agent/app/service/nginx_utils.go index 3a77f5060f28..8e665efb9bd1 100644 --- a/agent/app/service/nginx_utils.go +++ b/agent/app/service/nginx_utils.go @@ -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 { diff --git a/agent/app/service/website.go b/agent/app/service/website.go index d86d81a07c0d..cd570f09408e 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -1,12 +1,10 @@ package service import ( - "bufio" "bytes" "context" "crypto/x509" "encoding/base64" - "encoding/json" "encoding/pem" "errors" "fmt" @@ -46,7 +44,6 @@ import ( "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" - "golang.org/x/crypto/bcrypt" ) type WebsiteService struct { @@ -62,42 +59,29 @@ type IWebsiteService interface { DeleteWebsite(req request.WebsiteDelete) error GetWebsite(id uint) (response.WebsiteDTO, error) BatchOpWebsite(req request.BatchWebsiteOp) error - BatchSetGroup(req request.BatchWebsiteGroup) error - CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) - GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) - DeleteWebsiteDomain(domainId uint) error - UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error + BatchSetGroup(req request.BatchWebsiteGroup) error + ChangePHPVersion(req request.WebsitePHPVersionReq) error + OperateCrossSiteAccess(req request.CrossSiteAccessOp) error + ExecComposer(req request.ExecComposerReq) error + ChangeGroup(group, newGroup uint) error + ChangeDefaultServer(id uint) error + PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) + OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error) + UpdateStream(req request.StreamUpdate) error GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) UpdateNginxConfigByScope(req request.NginxConfigUpdate) error GetWebsiteNginxConfig(websiteId uint, configType string) (*response.FileInfo, error) UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error + GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) - OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error) - ChangeDefaultServer(id uint) error - PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) - - ChangePHPVersion(req request.WebsitePHPVersionReq) error - - GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) - UpdateRewriteConfig(req request.NginxRewriteUpdate) error - OperateCustomRewrite(req request.CustomRewriteOperate) error - ListCustomRewrite() ([]string, error) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) UpdateSiteDir(req request.WebsiteUpdateDir) error UpdateSitePermission(req request.WebsiteUpdateDirPermission) error - OperateProxy(req request.WebsiteProxyConfig) (err error) - GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) - UpdateProxyFile(req request.NginxProxyUpdate) (err error) - UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) - GetProxyCache(id uint) (res response.NginxProxyCache, err error) - ClearProxyCache(req request.NginxCommonReq) error - DeleteProxy(req request.WebsiteProxyDel) (err error) - UpdateCors(req request.CorsConfigReq) error GetCors(websiteID uint) (*request.CorsConfig, error) @@ -108,32 +92,44 @@ type IWebsiteService interface { GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) - GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) - UpdateAuthBasic(req request.NginxAuthUpdate) (err error) - GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) - UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error - UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) + SetRealIPConfig(req request.WebsiteRealIP) error + GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) + + GetWebsiteResource(websiteID uint) ([]response.Resource, error) + ListDatabases() ([]response.Database, error) + ChangeDatabase(req request.ChangeDatabase) error + GetLoadBalances(id uint) ([]dto.NginxUpstream, error) CreateLoadBalance(req request.WebsiteLBCreate) error DeleteLoadBalance(req request.WebsiteLBDelete) error UpdateLoadBalance(req request.WebsiteLBUpdate) error UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error - SetRealIPConfig(req request.WebsiteRealIP) error - GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) - - ChangeGroup(group, newGroup uint) error + OperateProxy(req request.WebsiteProxyConfig) (err error) + GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) + UpdateProxyFile(req request.NginxProxyUpdate) (err error) + UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) + GetProxyCache(id uint) (res response.NginxProxyCache, err error) + ClearProxyCache(req request.NginxCommonReq) error + DeleteProxy(req request.WebsiteProxyDel) (err error) - GetWebsiteResource(websiteID uint) ([]response.Resource, error) - ListDatabases() ([]response.Database, error) - ChangeDatabase(req request.ChangeDatabase) error + CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) + GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) + DeleteWebsiteDomain(domainId uint) error + UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error - OperateCrossSiteAccess(req request.CrossSiteAccessOp) error + GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) + UpdateRewriteConfig(req request.NginxRewriteUpdate) error + OperateCustomRewrite(req request.CustomRewriteOperate) error + ListCustomRewrite() ([]string, error) - ExecComposer(req request.ExecComposerReq) error + GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) + UpdateAuthBasic(req request.NginxAuthUpdate) (err error) + GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) + UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error } func NewIWebsiteService() IWebsiteService { @@ -263,28 +259,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) return err } defaultHttpPort := nginxInstall.HttpPort - var ( - domains []model.WebsiteDomain - ) - domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0) - if err != nil { - return err - } - primaryDomain := domains[0].Domain - if domains[0].Port != defaultHttpPort { - primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port) - } - defaultDate, _ := time.Parse(constant.DateLayout, constant.WebsiteDefaultExpireDate) + website := &model.Website{ - PrimaryDomain: primaryDomain, Type: create.Type, Alias: alias, Remark: create.Remark, Status: constant.WebRunning, ExpireDate: defaultDate, WebsiteGroupID: create.WebsiteGroupID, - Protocol: constant.ProtocolHTTP, Proxy: create.Proxy, SiteDir: "/", AccessLog: true, @@ -293,9 +276,34 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) } var ( - appInstall *model.AppInstall - runtime *model.Runtime + domains []model.WebsiteDomain + appInstall *model.AppInstall + runtime *model.Runtime + primaryDomain string ) + if website.Type == constant.Stream { + website.PrimaryDomain = create.Name + website.Protocol = constant.ProtocolStream + website.StreamPorts = create.StreamConfig.StreamPorts + ports := strings.Split(create.StreamConfig.StreamPorts, ",") + for _, port := range ports { + portNum, _ := strconv.Atoi(port) + if err = checkWebsitePort(nginxInstall.HttpsPort, portNum, website.Type); err != nil { + return err + } + } + } else { + domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0) + if err != nil { + return err + } + primaryDomain = domains[0].Domain + if domains[0].Port != defaultHttpPort { + primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port) + } + website.PrimaryDomain = primaryDomain + website.Protocol = constant.ProtocolHTTP + } createTask, err := task.NewTaskWithOps(primaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0) if err != nil { @@ -429,33 +437,39 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) } configNginx := func(t *task.Task) error { - if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { - return err - } - if err = createWafConfig(website, domains); err != nil { + if err = configDefaultNginx(website, domains, appInstall, runtime, create.StreamConfig); err != nil { return err } - if create.Type == constant.Runtime { - runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID)) - if err != nil { + if website.Type != constant.Stream { + if err = createWafConfig(website, domains); err != nil { return err } - if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore { - createOpenBasedirConfig(website) + if create.Type == constant.Runtime { + runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore { + createOpenBasedirConfig(website) + } } } + tx, ctx := helper.GetTxAndContext() defer tx.Rollback() if err = websiteRepo.Create(ctx, website); err != nil { return err } t.Task.ResourceID = website.ID - for i := range domains { - domains[i].WebsiteID = website.ID - } - if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { - return err + if len(domains) > 0 { + for i := range domains { + domains[i].WebsiteID = website.ID + } + if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { + return err + } } + tx.Commit() return nil } @@ -637,7 +651,9 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) { res.AccessLogPath = GetSitePath(website, SiteAccessLog) res.SitePath = GetSitePath(website, SiteDir) res.SiteDir = website.SiteDir - if website.Type == constant.Runtime { + fileOp := files.NewFileOp() + switch website.Type { + case constant.Runtime: runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) if err != nil { return res, err @@ -645,7 +661,28 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) { res.RuntimeType = runtime.Type res.RuntimeName = runtime.Name if runtime.Type == constant.RuntimePHP { - res.OpenBaseDir = files.NewFileOp().Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + res.OpenBaseDir = fileOp.Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + } + case constant.Stream: + nginxParser, err := parser.NewParser(GetSitePath(website, StreamConf)) + if err != nil { + return res, err + } + config, err := nginxParser.Parse() + if err != nil { + return res, err + } + upstreams := config.FindUpstreams() + for _, up := range upstreams { + directives := up.GetDirectives() + for _, d := range directives { + dName := d.GetName() + if _, ok := dto.LBAlgorithms[dName]; ok { + res.Algorithm = dName + } + } + res.Servers = getNginxUpstreamServers(up.UpstreamServers) + break } } return res, nil @@ -700,8 +737,10 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error { return err } - if err = delWafConfig(website, req.ForceDelete); err != nil { - return err + if website.Type != constant.Stream { + if err = delWafConfig(website, req.ForceDelete); err != nil { + return err + } } if checkIsLinkApp(website) && req.DeleteApp { @@ -773,177 +812,6 @@ func (w WebsiteService) UpdateWebsiteDomain(req request.WebsiteDomainUpdate) err return websiteDomainRepo.Save(context.TODO(), &domain) } -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)) -} - func (w WebsiteService) GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { @@ -1000,7 +868,7 @@ func (w WebsiteService) GetWebsiteNginxConfig(websiteID uint, configType string) configPath := "" switch configType { case constant.AppOpenresty: - configPath = GetSitePath(website, SiteConf) + configPath = GetWebsiteConfigPath(website) } info, err := files.NewFileInfo(files.FileOption{ Path: configPath, @@ -1524,876 +1392,95 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error return websiteRepo.Save(context.Background(), &website) } -func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error { - website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) +func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) 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 - } + runDir := req.SiteDir + siteDir := path.Join("/www/sites", website.Alias, "index") + if req.SiteDir != "/" { + siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir) } - if err := fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil { return err } + website.SiteDir = runDir + return websiteRepo.Save(context.Background(), &website) +} - if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{includePath}}}, &website); err != nil { - _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm) +func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermission) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + absoluteIndexPath := GetSitePath(website, SiteIndexDir) + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil { return err } - website.Rewrite = req.Name + website.User = req.User + website.Group = req.Group return websiteRepo.Save(context.Background(), &website) } -func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) { +func (w WebsiteService) UpdateCors(req request.CorsConfigReq) 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 -} - -func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error { - website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) - if err != nil { - return err - } - runDir := req.SiteDir - siteDir := path.Join("/www/sites", website.Alias, "index") - if req.SiteDir != "/" { - siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir) - } - if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil { - return err - } - website.SiteDir = runDir - return websiteRepo.Save(context.Background(), &website) -} - -func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermission) error { - website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) - if err != nil { - return err - } - absoluteIndexPath := GetSitePath(website, SiteIndexDir) - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) - if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil { - return err - } - website.User = req.User - website.Group = req.Group - return websiteRepo.Save(context.Background(), &website) -} - -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 if req.CacheTime == 0 { - location.RemoveBrowserCache() - } else { - location.AddBroswerNoCache() - } - // 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 - 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) -} - -func (w WebsiteService) UpdateCors(req request.CorsConfigReq) error { - website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) - if err != nil { - return err + return err } params := []dto.NginxParam{ {Name: "add_header", Params: []string{"Access-Control-Allow-Origin"}}, {Name: "add_header", Params: []string{"Access-Control-Allow-Methods"}}, - {Name: "add_header", Params: []string{"Access-Control-Allow-Headers"}}, - {Name: "add_header", Params: []string{"Access-Control-Allow-Credentials"}}, - {Name: "if", Params: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}}, - } - if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { - return err - } - if req.Cors { - return updateWebsiteConfig(website, func(server *components.Server) error { - server.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"}) - if req.AllowMethods != "" { - server.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"}) - } - if req.AllowHeaders != "" { - server.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"}) - } - if req.AllowCredentials { - server.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"}) - } - if req.Preflight { - server.AddCorsOption() - } - return nil - }) - } - return nil -} - -func (w WebsiteService) GetCors(websiteID uint) (*request.CorsConfig, error) { - website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) - if err != nil { - return nil, err - } - server, err := getServer(website) - if err != nil { - return nil, err - } - if server == nil { - return nil, nil - } - cors := &request.CorsConfig{ - Cors: server.Cors, - AllowOrigins: server.AllowOrigins, - AllowMethods: server.AllowMethods, - AllowHeaders: server.AllowHeaders, - AllowCredentials: server.AllowCredentials, - Preflight: server.Preflight, - } - return cors, nil -} - -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 + {Name: "add_header", Params: []string{"Access-Control-Allow-Headers"}}, + {Name: "add_header", Params: []string{"Access-Control-Allow-Credentials"}}, + {Name: "if", Params: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}}, } - - entries, err := os.ReadDir(absoluteAuthDir) - if err != nil { - return nil, err + if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { + return 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 + if req.Cors { + return updateWebsiteConfig(website, func(server *components.Server) error { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"}) + if req.AllowMethods != "" { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"}) } - config, err := parser.NewStringParser(string(content)).Parse() - if err != nil { - return nil, err + if req.AllowHeaders != "" { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"}) } - 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 + if req.AllowCredentials { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"}) } - 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] - } + if req.Preflight { + server.AddCorsOption() } - res = append(res, response.NginxPathAuthRes{ - NginxPathAuth: pathAuth, - }) - } + return nil + }) } - return + return nil } -func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error { - website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) +func (w WebsiteService) GetCors(websiteID uint) (*request.CorsConfig, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(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 - } + return nil, 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) + server, err := getServer(website) 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) + return nil, err } - _ = fileOp.SaveFile(passPath, line, constant.DirPerm) - if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { - return buserr.WithErr("ErrUpdateBuWebsite", err) + if server == nil { + return nil, nil } - 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 + cors := &request.CorsConfig{ + Cors: server.Cors, + AllowOrigins: server.AllowOrigins, + AllowMethods: server.AllowMethods, + AllowHeaders: server.AllowHeaders, + AllowCredentials: server.AllowCredentials, + Preflight: server.Preflight, } - return nil + return cors, nil } func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) { @@ -3049,216 +2136,6 @@ func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error { return fileOp.SaveFile(resourcePath, req.Content, constant.DirPerm) } -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) -} - func (w WebsiteService) ChangeGroup(group, newGroup uint) error { return websiteRepo.UpdateGroup(group, newGroup) } @@ -3521,3 +2398,57 @@ func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error { }() return nil } + +func (w WebsiteService) UpdateStream(req request.StreamUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + website.StreamPorts = req.StreamConfig.StreamPorts + ports := strings.Split(req.StreamConfig.StreamPorts, ",") + for _, port := range ports { + portNum, _ := strconv.Atoi(port) + if err = checkWebsitePort(nginxFull.Install.HttpsPort, portNum, website.Type); err != nil { + return err + } + } + + config := nginxFull.SiteConfig.Config + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + server.Listens = []*components.ServerListen{} + for _, port := range ports { + server.UpdateListen(port, false) + if website.IPV6 { + server.UpdateListen("[::]:"+port, false) + } + } + upstream := components.Upstream{ + UpstreamName: website.Alias, + } + if req.Algorithm != "default" { + upstream.UpdateDirective(req.Algorithm, []string{}) + } + upstream.UpstreamServers = parseUpstreamServers(req.Servers) + for i, dir := range config.Block.Directives { + if dir.GetName() == "upstream" && dir.GetParameters()[0] == website.Alias { + config.Block.Directives[i] = &upstream + } + } + + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + if err = nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName); err != nil { + return err + } + website.StreamPorts = req.StreamConfig.StreamPorts + return websiteRepo.Save(context.Background(), &website) +} diff --git a/agent/app/service/website_auth_basic.go b/agent/app/service/website_auth_basic.go new file mode 100644 index 000000000000..3d1c803528ca --- /dev/null +++ b/agent/app/service/website_auth_basic.go @@ -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 +} diff --git a/agent/app/service/website_domain.go b/agent/app/service/website_domain.go new file mode 100644 index 000000000000..1bf2e0076146 --- /dev/null +++ b/agent/app/service/website_domain.go @@ -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)) +} diff --git a/agent/app/service/website_lb.go b/agent/app/service/website_lb.go new file mode 100644 index 000000000000..9094d5961e93 --- /dev/null +++ b/agent/app/service/website_lb.go @@ -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) +} diff --git a/agent/app/service/website_proxy.go b/agent/app/service/website_proxy.go new file mode 100644 index 000000000000..7ee6f6e25aef --- /dev/null +++ b/agent/app/service/website_proxy.go @@ -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) +} diff --git a/agent/app/service/website_rewrite.go b/agent/app/service/website_rewrite.go new file mode 100644 index 000000000000..04b22e1af4d1 --- /dev/null +++ b/agent/app/service/website_rewrite.go @@ -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 +} diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index b92c18e353e5..bf0c9803413c 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -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}, "") + 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 + } + 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) } - 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)) + 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 } - 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 { + 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: diff --git a/agent/cmd/server/nginx_conf/stream_default.conf b/agent/cmd/server/nginx_conf/stream_default.conf new file mode 100644 index 000000000000..f2429517d0c5 --- /dev/null +++ b/agent/cmd/server/nginx_conf/stream_default.conf @@ -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; +} diff --git a/agent/constant/website.go b/agent/constant/website.go index e4a3d059d238..2f601517691d 100644 --- a/agent/constant/website.go +++ b/agent/constant/website.go @@ -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" diff --git a/agent/go.mod b/agent/go.mod index e362e4b509f0..2f4f6130a3dc 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -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 diff --git a/agent/go.sum b/agent/go.sum index c2d8f3d978b5..a5c43a242741 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -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= diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 17f95ac5f27e..a3a712413048 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -57,6 +57,7 @@ func InitAgentDB() { migrations.AddGPUMonitor, migrations.UpdateDatabaseMysql, migrations.InitIptablesStatus, + migrations.UpdateWebsite, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index f1d192ff41d5..7cb2db373f61 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -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{}) + }, +} diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go index bbd0f698c3df..876fc1edaea8 100644 --- a/agent/router/ro_website.go +++ b/agent/router/ro_website.go @@ -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) } } diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index ed998d282943..63cc0e93bf5a 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -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 { diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 2949a2347fdb..353f0ecbf2cf 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -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); +}; diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index 3f6b23fd7eba..40391478f74d 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -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', + }, ]; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index e39aa1027ff4..f91b4fe0c584 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index c7f22f19e94d..626f9993576f 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -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', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 9e798b431308..cb9b8f633c21 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2622,6 +2622,9 @@ const message = { batchOpreate: 'バッチ操作', batchOpreateHelper: 'ウェブサイトをバッチ{0}しますか?', + stream: 'TCP/UDP プロキシ', + streamPorts: '待ち受けポート', + streamPortsHelper: 'カンマで区切ってください(例:3306, 3307)', }, php: { short_open_tag: '短いタグサポート', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index ffbd5533b779..a23f5e9ebeda 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2576,6 +2576,9 @@ const message = { batchOpreate: '일괄 작업', batchOpreateHelper: '웹사이트를 일괄 {0}, 계속 작업하시겠습니까?', + stream: 'TCP/UDP 프록시', + streamPorts: '수신 포트', + streamPortsHelper: '쉼표로 구분하세요. 예: 3306, 3307', }, php: { short_open_tag: '짧은 태그 지원', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 25ea2c551c7b..a93c79dadfe9 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -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', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 96cef369d23b..4af0a350aa9e 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -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', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 7ae6cbee47a3..e433428ce5d3 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2678,6 +2678,9 @@ const message = { batchOpreate: 'Пакетная операция', batchOpreateHelper: 'Пакетное {0} веб-сайтов, продолжить операцию?', + stream: 'Прокси TCP/UDP', + streamPorts: 'Порты прослушивания', + streamPortsHelper: 'Разделяйте запятыми, например: 3306, 3307', }, php: { short_open_tag: 'Поддержка коротких тегов', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 5d4c8b8d46e3..c60ece4ec5ef 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -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', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index fdcce3e12037..0666aa833e3e 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2510,6 +2510,9 @@ const message = { batchOpreate: '批次操作', batchOpreateHelper: '批次{0}網站,是否繼續操作?', + stream: 'TCP/UDP 代理', + streamPorts: '監聽端口', + streamPortsHelper: '請用逗號分隔,例如:3306,3307', }, php: { short_open_tag: '短標籤支援', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 22e8908344f2..8769674cd66c 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2506,6 +2506,9 @@ const message = { batchOpreate: '批量操作', batchOpreateHelper: '批量{0}网站,是否继续操作?', + stream: 'TCP/UDP 代理', + streamPorts: '监听端口', + streamPortsHelper: '请按逗号分割,例如:3306,3307', }, php: { short_open_tag: '短标签支持', diff --git a/frontend/src/views/website/website/components/website-ssl/index.vue b/frontend/src/views/website/website/components/website-ssl/index.vue new file mode 100644 index 000000000000..97a8b73ae71f --- /dev/null +++ b/frontend/src/views/website/website/components/website-ssl/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/https/index.vue b/frontend/src/views/website/website/config/basic/https/index.vue index 7f041617483a..6abdca88aded 100644 --- a/frontend/src/views/website/website/config/basic/https/index.vue +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -133,32 +133,7 @@ - - - {{ websiteSSL.primaryDomain }} - - - {{ websiteSSL.domains }} - - - {{ websiteSSL.organization }} - - - {{ getProvider(websiteSSL.provider) }} - - - {{ websiteSSL.acmeAccount.email }} - - - {{ dateFormatSimple(websiteSSL.expireDate) }} - - - {{ websiteSSL.description }} - - + {{ $t('website.SSLProConfig') }} @@ -191,15 +166,17 @@ + + diff --git a/frontend/src/views/website/website/config/basic/load-balance/index.vue b/frontend/src/views/website/website/config/basic/load-balance/index.vue index 6e039b36d05f..82bf00f5ecda 100644 --- a/frontend/src/views/website/website/config/basic/load-balance/index.vue +++ b/frontend/src/views/website/website/config/basic/load-balance/index.vue @@ -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 = [ { diff --git a/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue b/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue index 56fd99c0fef8..c28598477f61 100644 --- a/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue +++ b/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue @@ -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" > - - - - - - - - - {{ getHelper(item.algorithm) }} - + -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - {{ $t('commons.button.add') + $t('website.server') }} - -
-
- - diff --git a/frontend/src/views/website/website/config/basic/stream/index.vue b/frontend/src/views/website/website/config/basic/stream/index.vue new file mode 100644 index 000000000000..da7cc5b2ee91 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/stream/index.vue @@ -0,0 +1,73 @@ + + + diff --git a/frontend/src/views/website/website/config/index.vue b/frontend/src/views/website/website/config/index.vue index 8b56b00c7cbb..42ee786ff669 100644 --- a/frontend/src/views/website/website/config/index.vue +++ b/frontend/src/views/website/website/config/index.vue @@ -30,7 +30,7 @@ @@ -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(); });