mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 16:29:17 +08:00
feat: 网站子目录绑定域名 (#6098)
Refs https://github.com/1Panel-dev/1Panel/issues/2336 Refs https://github.com/1Panel-dev/1Panel/issues/6024
This commit is contained in:
parent
e0f628c59f
commit
c7c5f75d17
@ -52,9 +52,13 @@ func (b *BaseApi) GetWebsites(c *gin.Context) {
|
|||||||
// @Description 获取网站列表
|
// @Description 获取网站列表
|
||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /websites/options [get]
|
// @Router /websites/options [post]
|
||||||
func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
|
func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
|
||||||
websites, err := websiteService.GetWebsiteOptions()
|
var req request.WebsiteOptionReq
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
websites, err := websiteService.GetWebsiteOptions(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
return
|
return
|
||||||
|
@ -27,8 +27,9 @@ type WebsiteCreate struct {
|
|||||||
AppID uint `json:"appID"`
|
AppID uint `json:"appID"`
|
||||||
AppInstallID uint `json:"appInstallID"`
|
AppInstallID uint `json:"appInstallID"`
|
||||||
|
|
||||||
RuntimeID uint `json:"runtimeID"`
|
RuntimeID uint `json:"runtimeID"`
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
|
ParentWebsiteID uint `json:"parentWebsiteID"`
|
||||||
|
|
||||||
RuntimeConfig
|
RuntimeConfig
|
||||||
FtpConfig
|
FtpConfig
|
||||||
@ -36,6 +37,10 @@ type WebsiteCreate struct {
|
|||||||
SSLConfig
|
SSLConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebsiteOptionReq struct {
|
||||||
|
Types []string `json:"types"`
|
||||||
|
}
|
||||||
|
|
||||||
type RuntimeConfig struct {
|
type RuntimeConfig struct {
|
||||||
ProxyType string `json:"proxyType"`
|
ProxyType string `json:"proxyType"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
|
@ -32,6 +32,7 @@ type WebsiteRes struct {
|
|||||||
SSLExpireDate time.Time `json:"sslExpireDate"`
|
SSLExpireDate time.Time `json:"sslExpireDate"`
|
||||||
SSLStatus string `json:"sslStatus"`
|
SSLStatus string `json:"sslStatus"`
|
||||||
AppInstallID uint `json:"appInstallId"`
|
AppInstallID uint `json:"appInstallId"`
|
||||||
|
ChildSites []string `json:"childSites"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsiteOption struct {
|
type WebsiteOption struct {
|
||||||
|
@ -4,32 +4,33 @@ import "time"
|
|||||||
|
|
||||||
type Website struct {
|
type Website struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Protocol string `gorm:"type:varchar;not null" json:"protocol"`
|
Protocol string `gorm:"not null" json:"protocol"`
|
||||||
PrimaryDomain string `gorm:"type:varchar;not null" json:"primaryDomain"`
|
PrimaryDomain string `gorm:"not null" json:"primaryDomain"`
|
||||||
Type string `gorm:"type:varchar;not null" json:"type"`
|
Type string `gorm:"not null" json:"type"`
|
||||||
Alias string `gorm:"type:varchar;not null" json:"alias"`
|
Alias string `gorm:"not null" json:"alias"`
|
||||||
Remark string `gorm:"type:longtext;" json:"remark"`
|
Remark string `json:"remark"`
|
||||||
Status string `gorm:"type:varchar;not null" json:"status"`
|
Status string `gorm:"not null" json:"status"`
|
||||||
HttpConfig string `gorm:"type:varchar;not null" json:"httpConfig"`
|
HttpConfig string `gorm:"not null" json:"httpConfig"`
|
||||||
ExpireDate time.Time `json:"expireDate"`
|
ExpireDate time.Time `json:"expireDate"`
|
||||||
|
|
||||||
Proxy string `gorm:"type:varchar;" json:"proxy"`
|
Proxy string `json:"proxy"`
|
||||||
ProxyType string `gorm:"type:varchar;" json:"proxyType"`
|
ProxyType string `json:"proxyType"`
|
||||||
SiteDir string `gorm:"type:varchar;" json:"siteDir"`
|
SiteDir string `json:"siteDir"`
|
||||||
ErrorLog bool `json:"errorLog"`
|
ErrorLog bool `json:"errorLog"`
|
||||||
AccessLog bool `json:"accessLog"`
|
AccessLog bool `json:"accessLog"`
|
||||||
DefaultServer bool `json:"defaultServer"`
|
DefaultServer bool `json:"defaultServer"`
|
||||||
IPV6 bool `json:"IPV6"`
|
IPV6 bool `json:"IPV6"`
|
||||||
Rewrite string `gorm:"type:varchar" json:"rewrite"`
|
Rewrite string `json:"rewrite"`
|
||||||
|
|
||||||
WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"`
|
WebsiteGroupID uint `json:"webSiteGroupId"`
|
||||||
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
|
WebsiteSSLID uint `json:"webSiteSSLId"`
|
||||||
RuntimeID uint `gorm:"type:integer" json:"runtimeID"`
|
RuntimeID uint `json:"runtimeID"`
|
||||||
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
|
AppInstallID uint `json:"appInstallId"`
|
||||||
FtpID uint `gorm:"type:integer" json:"ftpId"`
|
FtpID uint `json:"ftpId"`
|
||||||
|
ParentWebsiteID uint `json:"parentWebsiteID"`
|
||||||
|
|
||||||
User string `gorm:"type:varchar;" json:"user"`
|
User string `json:"user"`
|
||||||
Group string `gorm:"type:varchar;" json:"group"`
|
Group string `json:"group"`
|
||||||
|
|
||||||
DbType string `json:"dbType"`
|
DbType string `json:"dbType"`
|
||||||
DbID uint `json:"dbID"`
|
DbID uint `json:"dbID"`
|
||||||
|
@ -19,6 +19,9 @@ type IWebsiteRepo interface {
|
|||||||
WithDomainLike(domain string) DBOption
|
WithDomainLike(domain string) DBOption
|
||||||
WithRuntimeID(runtimeID uint) DBOption
|
WithRuntimeID(runtimeID uint) DBOption
|
||||||
WithIDs(ids []uint) DBOption
|
WithIDs(ids []uint) DBOption
|
||||||
|
WithTypes(types []string) DBOption
|
||||||
|
WithParentID(websiteID uint) DBOption
|
||||||
|
|
||||||
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
|
||||||
List(opts ...DBOption) ([]model.Website, error)
|
List(opts ...DBOption) ([]model.Website, error)
|
||||||
GetFirst(opts ...DBOption) (model.Website, error)
|
GetFirst(opts ...DBOption) (model.Website, error)
|
||||||
@ -49,6 +52,12 @@ func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteRepo) WithTypes(types []string) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("type in (?)", types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WebsiteRepo) WithRuntimeID(runtimeID uint) DBOption {
|
func (w *WebsiteRepo) WithRuntimeID(runtimeID uint) DBOption {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("runtime_id = ?", runtimeID)
|
return db.Where("runtime_id = ?", runtimeID)
|
||||||
@ -79,6 +88,12 @@ func (w *WebsiteRepo) WithWebsiteSSLID(sslId uint) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WebsiteRepo) WithParentID(websiteID uint) DBOption {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("parent_website_id = ?", websiteID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WebsiteRepo) WithGroupID(groupId uint) DBOption {
|
func (w *WebsiteRepo) WithGroupID(groupId uint) DBOption {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("website_group_id = ?", groupId)
|
return db.Where("website_group_id = ?", groupId)
|
||||||
|
@ -59,7 +59,7 @@ type IWebsiteService interface {
|
|||||||
GetWebsites() ([]response.WebsiteDTO, error)
|
GetWebsites() ([]response.WebsiteDTO, error)
|
||||||
CreateWebsite(create request.WebsiteCreate) error
|
CreateWebsite(create request.WebsiteCreate) error
|
||||||
OpWebsite(req request.WebsiteOp) error
|
OpWebsite(req request.WebsiteOp) error
|
||||||
GetWebsiteOptions() ([]response.WebsiteOption, error)
|
GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error)
|
||||||
UpdateWebsite(req request.WebsiteUpdate) error
|
UpdateWebsite(req request.WebsiteUpdate) error
|
||||||
DeleteWebsite(req request.WebsiteDelete) error
|
DeleteWebsite(req request.WebsiteDelete) error
|
||||||
GetWebsite(id uint) (response.WebsiteDTO, error)
|
GetWebsite(id uint) (response.WebsiteDTO, error)
|
||||||
@ -170,7 +170,7 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
|||||||
}
|
}
|
||||||
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
|
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias)
|
||||||
|
|
||||||
websiteDTOs = append(websiteDTOs, response.WebsiteRes{
|
siteDTO := response.WebsiteRes{
|
||||||
ID: web.ID,
|
ID: web.ID,
|
||||||
CreatedAt: web.CreatedAt,
|
CreatedAt: web.CreatedAt,
|
||||||
Protocol: web.Protocol,
|
Protocol: web.Protocol,
|
||||||
@ -186,7 +186,15 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
|
|||||||
RuntimeName: runtimeName,
|
RuntimeName: runtimeName,
|
||||||
SitePath: sitePath,
|
SitePath: sitePath,
|
||||||
AppInstallID: appInstallID,
|
AppInstallID: appInstallID,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
sites, _ := websiteRepo.List(websiteRepo.WithParentID(web.ID))
|
||||||
|
if len(sites) > 0 {
|
||||||
|
for _, site := range sites {
|
||||||
|
siteDTO.ChildSites = append(siteDTO.ChildSites, site.PrimaryDomain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
websiteDTOs = append(websiteDTOs, siteDTO)
|
||||||
}
|
}
|
||||||
return total, websiteDTOs, nil
|
return total, websiteDTOs, nil
|
||||||
}
|
}
|
||||||
@ -198,9 +206,10 @@ func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, web := range websites {
|
for _, web := range websites {
|
||||||
websiteDTOs = append(websiteDTOs, response.WebsiteDTO{
|
res := response.WebsiteDTO{
|
||||||
Website: web,
|
Website: web,
|
||||||
})
|
}
|
||||||
|
websiteDTOs = append(websiteDTOs, res)
|
||||||
}
|
}
|
||||||
return websiteDTOs, nil
|
return websiteDTOs, nil
|
||||||
}
|
}
|
||||||
@ -364,37 +373,47 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
website.RuntimeID = runtime.ID
|
website.RuntimeID = runtime.ID
|
||||||
if runtime.Type == constant.RuntimePHP {
|
|
||||||
var (
|
switch runtime.Type {
|
||||||
req request.AppInstallCreate
|
case constant.RuntimePHP:
|
||||||
install *model.AppInstall
|
if runtime.Resource == constant.ResourceAppstore {
|
||||||
)
|
var (
|
||||||
reg, _ := regexp.Compile(`[^a-z0-9_-]+`)
|
req request.AppInstallCreate
|
||||||
req.Name = reg.ReplaceAllString(strings.ToLower(alias), "")
|
install *model.AppInstall
|
||||||
req.AppDetailId = create.AppInstall.AppDetailId
|
)
|
||||||
req.Params = create.AppInstall.Params
|
reg, _ := regexp.Compile(`[^a-z0-9_-]+`)
|
||||||
req.Params["IMAGE_NAME"] = runtime.Image
|
req.Name = reg.ReplaceAllString(strings.ToLower(alias), "")
|
||||||
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
req.AppDetailId = create.AppInstall.AppDetailId
|
||||||
req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www")
|
req.Params = create.AppInstall.Params
|
||||||
install, err = NewIAppService().Install(req)
|
req.Params["IMAGE_NAME"] = runtime.Image
|
||||||
if err != nil {
|
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
||||||
return err
|
req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www")
|
||||||
|
install, err = NewIAppService().Install(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
website.AppInstallID = install.ID
|
||||||
|
appInstall = install
|
||||||
|
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||||
|
} else {
|
||||||
|
website.ProxyType = create.ProxyType
|
||||||
|
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||||
|
proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock"))
|
||||||
|
}
|
||||||
|
if website.ProxyType == constant.RuntimeProxyTcp {
|
||||||
|
proxy = fmt.Sprintf("127.0.0.1:%d", create.Port)
|
||||||
|
}
|
||||||
|
website.Proxy = proxy
|
||||||
}
|
}
|
||||||
website.AppInstallID = install.ID
|
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||||
appInstall = install
|
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
||||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
|
||||||
} else {
|
|
||||||
website.ProxyType = create.ProxyType
|
|
||||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
|
||||||
proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock"))
|
|
||||||
}
|
|
||||||
if website.ProxyType == constant.RuntimeProxyTcp {
|
|
||||||
proxy = fmt.Sprintf("127.0.0.1:%d", create.Port)
|
|
||||||
}
|
|
||||||
website.Proxy = proxy
|
|
||||||
}
|
}
|
||||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
case constant.Subsite:
|
||||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
parentWebsite, err := websiteRepo.GetFirst(commonRepo.WithByID(create.ParentWebsiteID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
website.ParentWebsiteID = parentWebsite.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 {
|
if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 {
|
||||||
@ -489,8 +508,12 @@ func (w WebsiteService) OpWebsite(req request.WebsiteOp) error {
|
|||||||
return websiteRepo.Save(context.Background(), &website)
|
return websiteRepo.Save(context.Background(), &website)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebsiteService) GetWebsiteOptions() ([]response.WebsiteOption, error) {
|
func (w WebsiteService) GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) {
|
||||||
webs, err := websiteRepo.List()
|
var options []repo.DBOption
|
||||||
|
if len(req.Types) > 0 {
|
||||||
|
options = append(options, websiteRepo.WithTypes(req.Types))
|
||||||
|
}
|
||||||
|
webs, err := websiteRepo.List(options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -556,6 +579,16 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if website.Type != constant.Subsite {
|
||||||
|
parentWebsites, _ := websiteRepo.List(websiteRepo.WithParentID(website.ID))
|
||||||
|
if len(parentWebsites) > 0 {
|
||||||
|
var names []string
|
||||||
|
for _, site := range parentWebsites {
|
||||||
|
names = append(names, site.PrimaryDomain)
|
||||||
|
}
|
||||||
|
return buserr.WithName("ErrParentWebsite", strings.Join(names, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
if err = delNginxConfig(website, req.ForceDelete); err != nil {
|
if err = delNginxConfig(website, req.ForceDelete); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -2759,7 +2792,7 @@ func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*res
|
|||||||
}
|
}
|
||||||
res.Dirs = []string{"/"}
|
res.Dirs = []string{"/"}
|
||||||
for _, file := range indexFiles {
|
for _, file := range indexFiles {
|
||||||
if !file.IsDir() {
|
if !file.IsDir() || file.Name() == "node_modules" || file.Name() == "vendor" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name()))
|
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name()))
|
||||||
|
@ -231,12 +231,9 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
|||||||
case constant.RuntimePHP:
|
case constant.RuntimePHP:
|
||||||
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
server.UpdateDirective("error_page", []string{"404", "/404.html"})
|
||||||
if runtime.Resource == constant.ResourceLocal {
|
if runtime.Resource == constant.ResourceLocal {
|
||||||
switch runtime.Type {
|
server.UpdateRoot(rootIndex)
|
||||||
case constant.RuntimePHP:
|
localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php")
|
||||||
server.UpdateRoot(rootIndex)
|
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||||
localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php")
|
|
||||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
server.UpdateRoot(rootIndex)
|
server.UpdateRoot(rootIndex)
|
||||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||||
@ -245,6 +242,34 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
|||||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
|
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
|
||||||
server.UpdateRootProxy([]string{proxy})
|
server.UpdateRootProxy([]string{proxy})
|
||||||
}
|
}
|
||||||
|
case constant.Subsite:
|
||||||
|
parentWebsite, err := websiteRepo.GetFirst(commonRepo.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(commonRepo.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(nginxInstall.GetPath(), 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.FilePath = configPath
|
config.FilePath = configPath
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
Static = "static"
|
Static = "static"
|
||||||
Proxy = "proxy"
|
Proxy = "proxy"
|
||||||
Runtime = "runtime"
|
Runtime = "runtime"
|
||||||
|
Subsite = "subsite"
|
||||||
|
|
||||||
SSLExisted = "existed"
|
SSLExisted = "existed"
|
||||||
SSLAuto = "auto"
|
SSLAuto = "auto"
|
||||||
|
@ -94,6 +94,7 @@ ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the
|
|||||||
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
|
||||||
ErrDomainFormat: "{{ .name }} domain format error"
|
ErrDomainFormat: "{{ .name }} domain format error"
|
||||||
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
||||||
|
ErrParentWebsite: "You need to delete the subsite(s) {{ .name }} first"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
||||||
|
@ -94,6 +94,7 @@ ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可
|
|||||||
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
|
||||||
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||||
|
ErrParentWebsite: "需要先刪除子網站 {{ .name }}"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||||
|
@ -94,6 +94,7 @@ ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可
|
|||||||
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
|
||||||
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||||
|
ErrParentWebsite: "需要先删除子网站 {{ .name }}"
|
||||||
|
|
||||||
#ssl
|
#ssl
|
||||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||||
|
@ -290,7 +290,7 @@ var AddTask = &gormigrate.Migration{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var UpdateWebsite = &gormigrate.Migration{
|
var UpdateWebsite = &gormigrate.Migration{
|
||||||
ID: "20240807-update-website",
|
ID: "20240812-update-website",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
return tx.AutoMigrate(
|
return tx.AutoMigrate(
|
||||||
&model.Website{})
|
&model.Website{})
|
||||||
|
@ -19,7 +19,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
websiteRouter.POST("/operate", baseApi.OpWebsite)
|
websiteRouter.POST("/operate", baseApi.OpWebsite)
|
||||||
websiteRouter.POST("/log", baseApi.OpWebsiteLog)
|
websiteRouter.POST("/log", baseApi.OpWebsiteLog)
|
||||||
websiteRouter.POST("/check", baseApi.CreateWebsiteCheck)
|
websiteRouter.POST("/check", baseApi.CreateWebsiteCheck)
|
||||||
websiteRouter.GET("/options", baseApi.GetWebsiteOptions)
|
websiteRouter.POST("/options", baseApi.GetWebsiteOptions)
|
||||||
websiteRouter.POST("/update", baseApi.UpdateWebsite)
|
websiteRouter.POST("/update", baseApi.UpdateWebsite)
|
||||||
websiteRouter.GET("/:id", baseApi.GetWebsite)
|
websiteRouter.GET("/:id", baseApi.GetWebsite)
|
||||||
websiteRouter.POST("/del", baseApi.DeleteWebsite)
|
websiteRouter.POST("/del", baseApi.DeleteWebsite)
|
||||||
|
@ -17,4 +17,5 @@ type System struct {
|
|||||||
Entrance string `mapstructure:"entrance"`
|
Entrance string `mapstructure:"entrance"`
|
||||||
IsDemo bool `mapstructure:"is_demo"`
|
IsDemo bool `mapstructure:"is_demo"`
|
||||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||||
|
DbPath string `mapstructure:"db_path"`
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ export namespace Website {
|
|||||||
IPV6: boolean;
|
IPV6: boolean;
|
||||||
accessLog?: boolean;
|
accessLog?: boolean;
|
||||||
errorLog?: boolean;
|
errorLog?: boolean;
|
||||||
|
childSites?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebsiteDTO extends Website {
|
export interface WebsiteDTO extends Website {
|
||||||
@ -111,6 +112,10 @@ export namespace Website {
|
|||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OptionReq {
|
||||||
|
types?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface WebSiteLog {
|
export interface WebSiteLog {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
content: string;
|
content: string;
|
||||||
|
@ -38,8 +38,8 @@ export const GetWebsite = (id: number) => {
|
|||||||
return http.get<Website.WebsiteDTO>(`/websites/${id}`);
|
return http.get<Website.WebsiteDTO>(`/websites/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GetWebsiteOptions = () => {
|
export const GetWebsiteOptions = (req: Website.OptionReq) => {
|
||||||
return http.get<Array<string>>(`/websites/options`);
|
return http.post<any>(`/websites/options`, req);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GetWebsiteConfig = (id: number, type: string) => {
|
export const GetWebsiteConfig = (id: number, type: string) => {
|
||||||
|
@ -2134,6 +2134,10 @@ const message = {
|
|||||||
domainSSLHelper:
|
domainSSLHelper:
|
||||||
'Enabling SSL on a non-443 port will cause the 443 port to stop listening. If you need the 443 port to continue listening, please add the domain:443',
|
'Enabling SSL on a non-443 port will cause the 443 port to stop listening. If you need the 443 port to continue listening, please add the domain:443',
|
||||||
global: 'Global',
|
global: 'Global',
|
||||||
|
subsite: 'Subsite',
|
||||||
|
subsiteHelper: 'A subsite can select an existing PHP or static website directory as the main directory.',
|
||||||
|
parentWbeiste: 'Parent Website',
|
||||||
|
deleteSubsite: 'To delete the current website, you must first delete the subsite(s) {0}',
|
||||||
},
|
},
|
||||||
php: {
|
php: {
|
||||||
short_open_tag: 'Short tag support',
|
short_open_tag: 'Short tag support',
|
||||||
|
@ -1983,6 +1983,10 @@ const message = {
|
|||||||
generateDomain: '生成',
|
generateDomain: '生成',
|
||||||
domainSSLHelper: '非 443 端口開啟 SSL 會導致 443 端口移除監聽,如需 443 端口繼續監聽,請添加域名:443',
|
domainSSLHelper: '非 443 端口開啟 SSL 會導致 443 端口移除監聽,如需 443 端口繼續監聽,請添加域名:443',
|
||||||
global: '全局',
|
global: '全局',
|
||||||
|
subsite: '子網站',
|
||||||
|
subsiteHelper: '子網站可以選擇已存在的 PHP 和靜態網站的目錄作為主目錄。',
|
||||||
|
parentWbeiste: '父級網站',
|
||||||
|
deleteSubsite: '刪除當前網站需要先刪除子網站 {0}',
|
||||||
},
|
},
|
||||||
php: {
|
php: {
|
||||||
short_open_tag: '短標簽支持',
|
short_open_tag: '短標簽支持',
|
||||||
|
@ -1985,6 +1985,10 @@ const message = {
|
|||||||
generateDomain: '生成',
|
generateDomain: '生成',
|
||||||
domainSSLHelper: '非 443 端口开启 SSL 会导致 443 端口去掉监听,如需 443 端口继续监听,请添加域名:443',
|
domainSSLHelper: '非 443 端口开启 SSL 会导致 443 端口去掉监听,如需 443 端口继续监听,请添加域名:443',
|
||||||
global: '全局',
|
global: '全局',
|
||||||
|
subsite: '子网站',
|
||||||
|
subsiteHelper: '子网站可以选择已存在的 PHP 和静态网站的目录作为主目录',
|
||||||
|
parentWbeiste: '父级网站',
|
||||||
|
deleteSubsite: '删除当前网站需要先删除子网站 {0}',
|
||||||
},
|
},
|
||||||
php: {
|
php: {
|
||||||
short_open_tag: '短标签支持',
|
short_open_tag: '短标签支持',
|
||||||
|
@ -632,7 +632,7 @@ const loadAppInstalls = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadWebsites = async () => {
|
const loadWebsites = async () => {
|
||||||
const res = await GetWebsiteOptions();
|
const res = await GetWebsiteOptions({});
|
||||||
websiteOptions.value = res.data || [];
|
websiteOptions.value = res.data || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,17 +161,10 @@ const initData = () => {
|
|||||||
dirs.value = [];
|
dirs.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
function filterDirectories(directories: any[]) {
|
|
||||||
return directories.filter((dir) => {
|
|
||||||
return dir !== '/node_modules' && dir !== '/vendor';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDirConfig = async () => {
|
const getDirConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await GetDirConfig({ id: props.id });
|
const res = await GetDirConfig({ id: props.id });
|
||||||
dirs.value = res.data.dirs;
|
dirs.value = res.data.dirs;
|
||||||
dirs.value = filterDirectories(dirs.value);
|
|
||||||
dirConfig.value = res.data;
|
dirConfig.value = res.data;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
label: i18n.global.t('website.static'),
|
label: i18n.global.t('website.static'),
|
||||||
value: 'static',
|
value: 'static',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('website.subsite'),
|
||||||
|
value: 'subsite',
|
||||||
|
},
|
||||||
]"
|
]"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
>
|
>
|
||||||
@ -62,6 +66,12 @@
|
|||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
/>
|
/>
|
||||||
|
<el-alert
|
||||||
|
v-if="website.type == 'subsite'"
|
||||||
|
:title="$t('website.subsiteHelper')"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<el-form
|
<el-form
|
||||||
ref="websiteForm"
|
ref="websiteForm"
|
||||||
@ -147,6 +157,28 @@
|
|||||||
></Params>
|
></Params>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="website.type === 'subsite'">
|
||||||
|
<el-form-item :label="$t('website.parentWbeiste')" prop="parentWebsiteID">
|
||||||
|
<el-select v-model="website.parentWebsiteID" @change="getDirConfig(website.parentWebsiteID)">
|
||||||
|
<el-option
|
||||||
|
v-for="(site, index) in parentWebsites"
|
||||||
|
:key="index"
|
||||||
|
:label="site.primaryDomain"
|
||||||
|
:value="site.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('website.runDir')" prop="siteDir">
|
||||||
|
<el-select v-model="website.siteDir" filterable class="p-w-200">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in dirs"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
:key="index"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
<div v-if="website.type === 'runtime'">
|
<div v-if="website.type === 'runtime'">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
@ -496,7 +528,14 @@
|
|||||||
<script lang="ts" setup name="CreateWebSite">
|
<script lang="ts" setup name="CreateWebSite">
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { GetApp, GetAppDetail, SearchApp, GetAppInstalled, GetAppDetailByID } from '@/api/modules/app';
|
import { GetApp, GetAppDetail, SearchApp, GetAppInstalled, GetAppDetailByID } from '@/api/modules/app';
|
||||||
import { CreateWebsite, ListSSL, PreCheck, SearchAcmeAccount } from '@/api/modules/website';
|
import {
|
||||||
|
CreateWebsite,
|
||||||
|
GetWebsiteOptions,
|
||||||
|
ListSSL,
|
||||||
|
PreCheck,
|
||||||
|
SearchAcmeAccount,
|
||||||
|
GetDirConfig,
|
||||||
|
} from '@/api/modules/website';
|
||||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm, FormInstance } from 'element-plus';
|
import { ElForm, FormInstance } from 'element-plus';
|
||||||
@ -565,6 +604,8 @@ const initData = () => ({
|
|||||||
websiteSSLID: undefined,
|
websiteSSLID: undefined,
|
||||||
acmeAccountID: undefined,
|
acmeAccountID: undefined,
|
||||||
domains: [],
|
domains: [],
|
||||||
|
parentWebsiteID: undefined,
|
||||||
|
siteDir: '',
|
||||||
});
|
});
|
||||||
const website = ref(initData());
|
const website = ref(initData());
|
||||||
const rules = ref<any>({
|
const rules = ref<any>({
|
||||||
@ -594,6 +635,8 @@ const rules = ref<any>({
|
|||||||
dbPassword: [Rules.requiredInput, Rules.paramComplexity],
|
dbPassword: [Rules.requiredInput, Rules.paramComplexity],
|
||||||
dbHost: [Rules.requiredSelect],
|
dbHost: [Rules.requiredSelect],
|
||||||
websiteSSLID: [Rules.requiredSelect],
|
websiteSSLID: [Rules.requiredSelect],
|
||||||
|
parentWebsiteID: [Rules.requiredSelect],
|
||||||
|
siteDir: [Rules.requiredSelect],
|
||||||
});
|
});
|
||||||
|
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
@ -626,6 +669,8 @@ const taskLog = ref();
|
|||||||
const dbServices = ref();
|
const dbServices = ref();
|
||||||
const ssls = ref();
|
const ssls = ref();
|
||||||
const websiteSSL = ref();
|
const websiteSSL = ref();
|
||||||
|
const parentWebsites = ref();
|
||||||
|
const dirs = ref([]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
@ -656,6 +701,9 @@ const changeType = (type: string) => {
|
|||||||
website.value.proxyAddress = '';
|
website.value.proxyAddress = '';
|
||||||
searchAppInstalled('proxy');
|
searchAppInstalled('proxy');
|
||||||
break;
|
break;
|
||||||
|
case 'subsite':
|
||||||
|
listWebsites();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
website.value.appInstallId = undefined;
|
website.value.appInstallId = undefined;
|
||||||
break;
|
break;
|
||||||
@ -885,6 +933,27 @@ const changeAlias = (value: string) => {
|
|||||||
website.value.alias = domain;
|
website.value.alias = domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listWebsites = async () => {
|
||||||
|
try {
|
||||||
|
const res = await GetWebsiteOptions({ types: ['static', 'runtime'] });
|
||||||
|
parentWebsites.value = res.data;
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
website.value.parentWebsiteID = res.data[0].id;
|
||||||
|
getDirConfig(res.data[0].id);
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDirConfig = async (websiteID: number) => {
|
||||||
|
try {
|
||||||
|
const res = await GetDirConfig({ id: websiteID });
|
||||||
|
dirs.value = res.data.dirs;
|
||||||
|
if (res.data.dirs.length > 0) {
|
||||||
|
website.value.siteDir = res.data.dirs[0];
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
|
@ -23,16 +23,22 @@
|
|||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
{{ $t('website.deleteAppHelper') }}
|
{{ $t('website.deleteAppHelper') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="input-help" style="color: red" v-if="runtimeApp">
|
<span class="input-help text-red-500" v-if="runtimeApp">
|
||||||
{{ $t('website.deleteRuntimeHelper') }}
|
{{ $t('website.deleteRuntimeHelper') }}
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('website.deleteBackup')" />
|
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('website.deleteBackup')" />
|
||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
{{ $t('website.deleteBackupHelper') }}
|
{{ $t('website.deleteBackupHelper') }}
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="subSites != ''">
|
||||||
|
<span class="input-help text-red-500">
|
||||||
|
{{ $t('website.deleteSubsite', [subSites]) }}
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<span v-html="deleteHelper"></span>
|
<span v-html="deleteHelper"></span>
|
||||||
<el-input v-model="deleteInfo" :placeholder="websiteName" />
|
<el-input v-model="deleteInfo" :placeholder="websiteName" />
|
||||||
@ -74,6 +80,7 @@ const deleteInfo = ref('');
|
|||||||
const websiteName = ref('');
|
const websiteName = ref('');
|
||||||
const deleteHelper = ref('');
|
const deleteHelper = ref('');
|
||||||
const runtimeApp = ref(false);
|
const runtimeApp = ref(false);
|
||||||
|
const subSites = ref('');
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
@ -87,10 +94,14 @@ const acceptParams = async (website: Website.Website) => {
|
|||||||
deleteBackup: false,
|
deleteBackup: false,
|
||||||
forceDelete: false,
|
forceDelete: false,
|
||||||
};
|
};
|
||||||
|
subSites.value = '';
|
||||||
if (website.type === 'runtime' && website.appInstallId > 0) {
|
if (website.type === 'runtime' && website.appInstallId > 0) {
|
||||||
runtimeApp.value = true;
|
runtimeApp.value = true;
|
||||||
deleteReq.value.deleteApp = true;
|
deleteReq.value.deleteApp = true;
|
||||||
}
|
}
|
||||||
|
if (website.childSites && website.childSites.length > 0) {
|
||||||
|
subSites.value = website.childSites.join(',');
|
||||||
|
}
|
||||||
deleteInfo.value = '';
|
deleteInfo.value = '';
|
||||||
deleteReq.value.id = website.id;
|
deleteReq.value.id = website.id;
|
||||||
websiteName.value = website.primaryDomain;
|
websiteName.value = website.primaryDomain;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user