1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-14 01:34:47 +08:00

feat: PHP 多网站使用一个 PHP 容器 (#6338)

This commit is contained in:
zhengkunwang 2024-09-02 21:56:24 +08:00 committed by GitHub
parent 42f904e25a
commit 2538944701
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 553 additions and 379 deletions

View File

@ -160,3 +160,9 @@ type DelAppLink struct {
Install *model.AppInstall
ForceDelete bool
}
type PHPForm struct {
AdditionalProperties struct {
FormFields []interface{} `yaml:"formFields"`
} `yaml:"additionalProperties"`
}

View File

@ -29,6 +29,7 @@ type App struct {
Architectures string `json:"architectures"`
MemoryRequired int `json:"memoryRequired"`
GpuSupport bool `json:"gpuSupport"`
RequiredPanelVersion float64 `json:"requiredPanelVersion"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"tags" yaml:"tags" gorm:"-"`

View File

@ -19,6 +19,9 @@ type IAppRepo interface {
GetRecommend() DBOption
WithResource(resource string) DBOption
WithByLikeName(name string) DBOption
WithArch(arch string) DBOption
WithPanelVersion(panelVersion string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.App, error)
GetFirst(opts ...DBOption) (model.App, error)
GetBy(opts ...DBOption) ([]model.App, error)
@ -27,7 +30,6 @@ type IAppRepo interface {
Create(ctx context.Context, app *model.App) error
Save(ctx context.Context, app *model.App) error
BatchDelete(ctx context.Context, apps []model.App) error
WithArch(arch string) DBOption
}
func NewIAppRepo() IAppRepo {
@ -79,6 +81,12 @@ func (a AppRepo) WithArch(arch string) DBOption {
}
}
func (a AppRepo) WithPanelVersion(panelVersion string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("required_panel_version >= ?", panelVersion)
}
}
func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) {
var apps []model.App
db := getDb(opts...).Model(&model.App{})

View File

@ -70,6 +70,10 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
if req.Resource != "" && req.Resource != "all" {
opts = append(opts, appRepo.WithResource(req.Resource))
}
if req.Type == "php" {
info, _ := NewISettingService().GetSettingInfo()
opts = append(opts, appRepo.WithPanelVersion(info.SystemVersion))
}
if req.ShowCurrentArch {
info, err := NewIDashboardService().LoadOsInfo()
if err != nil {
@ -202,23 +206,22 @@ func (a AppService) GetAppDetail(appID uint, version, appType string) (response.
}
switch app.Type {
case constant.RuntimePHP:
buildPath := filepath.Join(versionPath, "build")
paramsPath := filepath.Join(buildPath, "config.json")
paramsPath := filepath.Join(versionPath, "data.yml")
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
return appDetailDTO, buserr.WithDetail(constant.ErrFileNotExist, paramsPath, nil)
}
param, err := fileOp.GetContent(paramsPath)
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err := json.Unmarshal(param, &paramMap); err != nil {
if err = yaml.Unmarshal(param, &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
composePath := filepath.Join(buildPath, "docker-compose.yml")
appDetailDTO.Params = paramMap["additionalProperties"]
composePath := filepath.Join(versionPath, "docker-compose.yml")
if !fileOp.Stat(composePath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
return appDetailDTO, buserr.WithDetail(constant.ErrFileNotExist, composePath, nil)
}
compose, err := fileOp.GetContent(composePath)
if err != nil {

View File

@ -716,6 +716,18 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
t.Log(i18n.GetMsgByKey("DeleteRuntimePHP"))
_ = fileOp.DeleteDir(path.Join(constant.RuntimeDir, "php"))
websites, _ := websiteRepo.List(commonRepo.WithByType("runtime"))
for _, website := range websites {
runtime, _ := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
if runtime != nil && runtime.Type == "php" {
website.Type = constant.Static
website.RuntimeID = 0
_ = websiteRepo.SaveWithoutCtx(&website)
}
}
_ = runtimeRepo.DeleteBy(commonRepo.WithByType("php"))
t.Log(i18n.GetMsgByKey("MoveSiteDirSuccess"))
}
@ -1153,6 +1165,7 @@ func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, t
if !ok {
app = model.App{}
}
app.RequiredPanelVersion = config.Version
app.Resource = constant.AppResourceRemote
app.Name = item.Name
app.Limit = config.Limit

View File

@ -699,7 +699,7 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
commandName := "docker"
commandArg := []string{"logs", container}
if containerType == "compose" {
commandName = "docker-compose"
commandName = "docker compose"
commandArg = []string{"-f", container, "logs"}
}
if tail != "0" {
@ -782,7 +782,7 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
commandName := "docker"
commandArg := []string{"logs", container}
if containerType == "compose" {
commandName = "docker-compose"
commandName = "docker compose"
commandArg = []string{"-f", container, "logs"}
}
if tail != "0" {

View File

@ -141,7 +141,7 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := u.loadPath(&req); err != nil {
return false, err
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
cmd := exec.Command("docker compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
return false, errors.New(string(stdout))

View File

@ -83,6 +83,10 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if exist != nil {
return nil, buserr.New(constant.ErrImageExist)
}
portValue, _ := create.Params["PANEL_APP_PORT_HTTP"]
if err := checkPortExist(int(portValue.(float64))); err != nil {
return nil, err
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New(constant.ErrPathNotFound)
@ -96,12 +100,12 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
return nil, err
}
}
}
if containerName, ok := create.Params["CONTAINER_NAME"]; ok {
if err := checkContainerName(containerName.(string)); err != nil {
return nil, err
}
}
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID))
if err != nil {
@ -130,6 +134,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
switch create.Type {
case constant.RuntimePHP:
runtime.Port = int(create.Params["PANEL_APP_PORT_HTTP"].(float64))
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
@ -263,6 +268,19 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
res.Source = v
}
res.Params = make(map[string]interface{})
for k, v := range envs {
if k == "PANEL_APP_PORT_HTTP" {
port, err := strconv.Atoi(v)
if err != nil {
return nil, err
}
res.Params[k] = port
continue
}
res.Params[k] = v
}
for _, form := range appForm.FormFields {
if v, ok := envs[form.EnvKey]; ok {
appParam := response.AppParam{
@ -373,18 +391,6 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return err
}
}
if containerName, ok := req.Params["CONTAINER_NAME"]; ok {
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return err
}
oldContainerName := envs["CONTAINER_NAME"]
if containerName != oldContainerName {
if err := checkContainerName(containerName.(string)); err != nil {
return err
}
}
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
if err != nil {
@ -405,6 +411,19 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
}
}
if containerName, ok := req.Params["CONTAINER_NAME"]; ok {
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return err
}
oldContainerName := envs["CONTAINER_NAME"]
if containerName != oldContainerName {
if err := checkContainerName(containerName.(string)); err != nil {
return err
}
}
}
projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
create := request.RuntimeCreate{
Image: req.Image,

View File

@ -2,7 +2,21 @@ package service
import (
"bytes"
"encoding/json"
"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/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
"github.com/pkg/errors"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"io"
"net/http"
"os"
@ -10,19 +24,6 @@ import (
"path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
"github.com/pkg/errors"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
)
func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) {
@ -66,30 +67,16 @@ func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fil
}
func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) {
buildDir := path.Join(appVersionDir, "build")
if !fileOp.Stat(buildDir) {
return buserr.New(constant.ErrDirNotFound)
}
runtimeDir := path.Join(constant.RuntimeDir, create.Type)
tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano()))
if err = fileOp.CopyDir(buildDir, tempDir); err != nil {
if err = fileOp.CopyDirWithNewName(appVersionDir, runtimeDir, create.Name); err != nil {
return
}
oldDir := path.Join(tempDir, "build")
projectDir := path.Join(runtimeDir, create.Name)
defer func() {
if err != nil {
_ = fileOp.DeleteDir(projectDir)
}
}()
if oldDir != projectDir {
if err = fileOp.Rename(oldDir, projectDir); err != nil {
return
}
if err = fileOp.DeleteDir(tempDir); err != nil {
return
}
}
composeContent, envContent, forms, err := handleParams(create, projectDir)
if err != nil {
return
@ -140,9 +127,9 @@ func reCreateRuntime(runtime *model.Runtime) {
}
func runComposeCmdWithLog(operate string, composePath string, logPath string) error {
cmd := exec.Command("docker-compose", "-f", composePath, operate)
cmd := exec.Command("docker compose", "-f", composePath, operate)
if operate == "up" {
cmd = exec.Command("docker-compose", "-f", composePath, operate, "-d")
cmd = exec.Command("docker compose", "-f", composePath, operate, "-d")
}
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
@ -218,7 +205,7 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
_ = logFile.Close()
}()
cmd := exec.Command("docker-compose", "-f", composePath, "build")
cmd := exec.Command("docker", "compose", "-f", composePath, "build")
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
cmd.Stdout = multiWriterStdout
var stderrBuf bytes.Buffer
@ -229,8 +216,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
if stderrBuf.String() == "" {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
}
} else {
runtime.Status = constant.RuntimeNormal
runtime.Message = ""
if oldImageID != "" {
client, err := docker.NewClient()
@ -272,6 +261,14 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
}
}
}
runtime.Status = constant.RuntimeStarting
_ = runtimeRepo.Save(runtime)
if out, err := compose.Up(composePath); err != nil {
runtime.Status = constant.RuntimeStartErr
runtime.Message = out
} else {
runtime.Status = constant.RuntimeRunning
}
}
_ = runtimeRepo.Save(runtime)
}
@ -293,7 +290,20 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
switch create.Type {
case constant.RuntimePHP:
create.Params["IMAGE_NAME"] = create.Image
forms, err = fileOp.GetContent(path.Join(projectDir, "config.json"))
var fromYml []byte
fromYml, err = fileOp.GetContent(path.Join(projectDir, "data.yml"))
if err != nil {
return
}
var data dto.PHPForm
err = yaml.Unmarshal(fromYml, &data)
if err != nil {
return
}
formFields := data.AdditionalProperties.FormFields
forms, err = json.MarshalIndent(map[string]interface{}{
"formFields": formFields,
}, "", " ")
if err != nil {
return
}
@ -307,6 +317,8 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
}
}
create.Params["CONTAINER_PACKAGE_URL"] = create.Source
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value
case constant.RuntimeNode:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["NODE_VERSION"] = create.Version

View File

@ -251,7 +251,7 @@ func restartCompose(composePath string) {
if _, err := os.Stat(pathItem); err != nil {
continue
}
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
upCmd := fmt.Sprintf("docker compose -f %s up -d", pathItem)
stdout, err := cmd.Exec(upCmd)
if err != nil {
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)

View File

@ -162,13 +162,11 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
appName = appInstall.Name
appInstallID = appInstall.ID
case constant.Runtime:
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
if err != nil {
return 0, nil, err
}
runtime, _ := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID))
if runtime != nil {
runtimeName = runtime.Name
runtimeType = runtime.Type
appInstallID = runtime.ID
}
}
sitePath := GetSitePath(web, SiteDir)
@ -375,24 +373,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
switch runtime.Type {
case constant.RuntimePHP:
if runtime.Resource == constant.ResourceAppstore {
var (
req request.AppInstallCreate
install *model.AppInstall
)
reg, _ := regexp.Compile(`[^a-z0-9_-]+`)
req.Name = reg.ReplaceAllString(strings.ToLower(alias), "")
req.AppDetailId = create.AppInstall.AppDetailId
req.Params = create.AppInstall.Params
req.Params["IMAGE_NAME"] = runtime.Image
req.AppContainerConfig = create.AppInstall.AppContainerConfig
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)
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
} else {
website.ProxyType = create.ProxyType
if website.ProxyType == constant.RuntimeProxyUnix {

View File

@ -42,15 +42,11 @@ func handleChineseDomain(domain string) (string, error) {
}
func createIndexFile(website *model.Website, runtime *model.Runtime) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
var (
indexPath string
indexContent string
websiteService = NewIWebsiteService()
indexFolder = path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index")
indexFolder = GetSitePath(*website, SiteIndexDir)
)
switch website.Type {
@ -132,9 +128,8 @@ func createProxyFile(website *model.Website) error {
return nil
}
func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, runtime *model.Runtime) error {
nginxFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name)
siteFolder := path.Join(nginxFolder, "www", "sites", website.Alias)
func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
siteFolder := GteSiteDir(website.Alias)
fileOp := files.NewFileOp()
if !fileOp.Stat(siteFolder) {
if err := fileOp.CreateDir(siteFolder, 0755); err != nil {
@ -185,11 +180,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
if err != nil {
return err
}
if err = createWebsiteFolder(nginxInstall, website, runtime); err != nil {
if err = createWebsiteFolder(website, runtime); err != nil {
return err
}
nginxFileName := website.Alias + ".conf"
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
configPath := GetSitePath(*website, SiteConf)
nginxContent := string(nginx_conf.WebsiteDefault)
config, err := parser.NewStringParser(nginxContent).Parse()
if err != nil {
@ -232,7 +226,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
server.UpdateDirective("error_page", []string{"404", "/404.html"})
if runtime.Resource == constant.ResourceLocal {
server.UpdateRoot(rootIndex)
localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php")
localPath := path.Join(GetSitePath(*website, SiteIndexDir), "index.php")
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
} else {
server.UpdateRoot(rootIndex)
@ -381,6 +375,21 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
}
func delNginxConfig(website model.Website, force bool) error {
configPath := GetSitePath(website, SiteConf)
fileOp := files.NewFileOp()
if !fileOp.Stat(configPath) {
return nil
}
if err := fileOp.DeleteFile(configPath); err != nil {
return err
}
sitePath := GteSiteDir(website.Alias)
if fileOp.Stat(sitePath) {
xpack.RemoveTamper(website.Alias)
_ = fileOp.DeleteDir(sitePath)
}
nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty))
if err != nil {
return err
@ -392,23 +401,6 @@ func delNginxConfig(website model.Website, force bool) error {
}
return err
}
nginxFileName := website.Alias + ".conf"
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
fileOp := files.NewFileOp()
if !fileOp.Stat(configPath) {
return nil
}
if err := fileOp.DeleteFile(configPath); err != nil {
return err
}
sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias)
if fileOp.Stat(sitePath) {
xpack.RemoveTamper(website.Alias)
_ = fileOp.DeleteDir(sitePath)
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
if force {
return nil

View File

@ -13,6 +13,7 @@ const (
RuntimeStopped = "stopped"
RuntimeUnhealthy = "unhealthy"
RuntimeCreating = "creating"
RuntimeStartErr = "startErr"
RuntimePHP = "php"
RuntimeNode = "node"

View File

@ -70,7 +70,7 @@ MoveSiteDir: "The current upgrade requires migrating the OpenResty website direc
MoveSiteToDir: "Migrate the website directory to {{ .name }}"
ErrMoveSiteDir: "Failed to migrate the website directory"
MoveSiteDirSuccess: "Successfully migrated the website directory"
DeleteRuntimePHP: "Delete PHP runtime environment"
#file
ErrFileCanNotRead: "File can not read"

View File

@ -71,7 +71,7 @@ MoveSiteDir: "當前升級需要遷移 OpenResty 網站目錄"
MoveSiteToDir: "遷移網站目錄到 {{ .name }}"
ErrMoveSiteDir: "遷移網站目錄失敗"
MoveSiteDirSuccess: "遷移網站目錄成功"
DeleteRuntimePHP: "刪除運行環境 PHP 版本"
#file
ErrFileCanNotRead: "此文件不支持預覽"

View File

@ -70,6 +70,7 @@ MoveSiteDir: "当前升级需要迁移 OpenResty 网站目录"
MoveSiteToDir: "迁移网站目录到 {{ .name }}"
ErrMoveSiteDir: "迁移网站目录失败"
MoveSiteDirSuccess: "迁移网站目录成功"
DeleteRuntimePHP: "删除 PHP 运行环境"
#file
ErrFileCanNotRead: "此文件不支持预览"

View File

@ -7,7 +7,8 @@ import (
)
func Init() {
go syncApp()
//TODO 国际化处理
//go syncApp()
go syncInstalledApp()
go syncRuntime()
go syncSSL()

View File

@ -15,12 +15,6 @@ func Init() {
migrations.InitImageRepo,
migrations.InitDefaultCA,
migrations.InitPHPExtensions,
migrations.AddTask,
migrations.UpdateWebsite,
migrations.UpdateWebsiteDomain,
migrations.UpdateApp,
migrations.AddTaskDB,
migrations.UpdateAppInstall,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -16,7 +16,7 @@ import (
)
var AddTable = &gormigrate.Migration{
ID: "20240826-add-table",
ID: "20240902-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.AppDetail{},
@ -54,6 +54,7 @@ var AddTable = &gormigrate.Migration{
&model.WebsiteDnsAccount{},
&model.WebsiteDomain{},
&model.WebsiteSSL{},
&model.Task{},
)
},
}
@ -210,52 +211,3 @@ var InitPHPExtensions = &gormigrate.Migration{
return nil
},
}
var AddTask = &gormigrate.Migration{
ID: "20240802-add-task",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.Task{})
},
}
var UpdateWebsite = &gormigrate.Migration{
ID: "20240812-update-website",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.Website{})
},
}
var UpdateWebsiteDomain = &gormigrate.Migration{
ID: "20240808-update-website-domain",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.WebsiteDomain{})
},
}
var AddTaskDB = &gormigrate.Migration{
ID: "20240822-add-task-table",
Migrate: func(tx *gorm.DB) error {
return global.TaskDB.AutoMigrate(
&model.Task{},
)
},
}
var UpdateApp = &gormigrate.Migration{
ID: "20240826-update-app",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.App{})
},
}
var UpdateAppInstall = &gormigrate.Migration{
ID: "20240828-update-app-install",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.AppInstall{})
},
}

View File

@ -43,12 +43,14 @@ func Start() {
server := &http.Server{
Handler: rootRouter,
}
if global.IsMaster {
_ = os.Remove("/tmp/agent.sock")
listener, err := net.Listen("unix", "/tmp/agent.sock")
if err != nil {
panic(err)
}
business.Init()
_ = server.Serve(listener)
return
} else {
@ -73,8 +75,8 @@ func Start() {
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.RequireAnyClientCert,
}
global.LOG.Info("listen at https://0.0.0.0:9999")
business.Init()
global.LOG.Info("listen at https://0.0.0.0:9999")
if err := server.ListenAndServeTLS("", ""); err != nil {
panic(err)
}

View File

@ -5,36 +5,36 @@ import (
)
func Pull(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s pull", filePath)
stdout, err := cmd.Execf("docker compose -f %s pull", filePath)
return stdout, err
}
func Up(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
stdout, err := cmd.Execf("docker compose -f %s up -d", filePath)
return stdout, err
}
func Down(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s down --remove-orphans", filePath)
stdout, err := cmd.Execf("docker compose -f %s down --remove-orphans", filePath)
return stdout, err
}
func Start(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s start", filePath)
stdout, err := cmd.Execf("docker compose -f %s start", filePath)
return stdout, err
}
func Stop(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s stop", filePath)
stdout, err := cmd.Execf("docker compose -f %s stop", filePath)
return stdout, err
}
func Restart(filePath string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s restart", filePath)
stdout, err := cmd.Execf("docker compose -f %s restart", filePath)
return stdout, err
}
func Operate(filePath, operation string) (string, error) {
stdout, err := cmd.Execf("docker-compose -f %s %s", filePath, operation)
stdout, err := cmd.Execf("docker compose -f %s %s", filePath, operation)
return stdout, err
}

View File

@ -438,6 +438,12 @@ func (f FileOp) CopyAndReName(src, dst, name string, cover bool) error {
}
}
func (f FileOp) CopyDirWithNewName(src, dst, newName string) error {
dstDir := filepath.Join(dst, newName)
str := fmt.Sprintf(`cp -rf '%s' '%s'`, src, dstDir)
return cmd.ExecCmd(str)
}
func (f FileOp) CopyDir(src, dst string) error {
srcInfo, err := f.Fs.Stat(src)
if err != nil {

View File

@ -77,6 +77,7 @@ export namespace App {
child?: FromFieldChild;
params?: FromParam[];
multiple?: boolean;
allowCreate?: boolean;
}
export interface FromFieldChild extends FromField {

View File

@ -2388,7 +2388,7 @@ const message = {
version: 'Version',
versionHelper: 'PHP version, e.g. v8.0',
buildHelper:
'The more extensions you select, the more CPU will be occupied during the image making process, so avoid selecting all extensions,If there is no extension you want, you can manually enter it and select it',
'The more extensions you select, the more CPU will be used during the image creation process. You can install extensions after the environment is created.',
openrestyWarn: 'PHP needs to be upgraded to OpenResty to version 1.21.4.1 or later to use',
toupgrade: 'To Upgrade',
edit: 'Edit runtime',

View File

@ -2217,7 +2217,7 @@ const message = {
localHelper: '本地運行環境需要自行安裝',
version: '版本',
versionHelper: 'PHP的版本, v8.0',
buildHelper: '選擇的擴展越多製作鏡像過程中占用 CPU 越多請盡量避免選擇全部擴展',
buildHelper: '選擇的擴展越多製作鏡像過程中占用 CPU 越多可以在創建完環境之後再安裝擴展',
openrestyWarn: 'PHP 需要升級 OpenResty 1.21.4.1 版本以上才能使用',
toupgrade: '去升級',
edit: '編輯運行環境',

View File

@ -2219,7 +2219,7 @@ const message = {
localHelper: '本地运行环境需要自行安装',
version: '版本',
versionHelper: 'PHP的版本, v8.0',
buildHelper: '选择的扩展越多制作镜像过程中占用 CPU 越多请尽量避免选择全部扩展',
buildHelper: '选择的扩展越多制作镜像过程中占用 CPU 越多可以在创建完环境之后再安装扩展',
openrestyWarn: 'PHP 需要升级 OpenResty 1.21.4.1 版本以上才能使用',
toupgrade: '去升级',
edit: '编辑运行环境',

View File

@ -42,7 +42,13 @@
{{ $t('app.toInstall') }}
</el-link>
</span>
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple" class="p-w-200">
<el-select
v-model="form[p.envKey]"
v-if="p.type == 'select'"
:multiple="p.multiple"
:allowCreate="p.allowCreate"
filterable
>
<el-option
v-for="service in p.values"
:key="service.label"

View File

@ -68,22 +68,73 @@
</el-row>
</el-form-item>
<div v-if="initParam">
<div v-if="runtime.type === 'php'">
<el-form-item
:label="getLabel(formFields['PHP_VERSION'])"
:rules="rules.params.PHP_VERSION"
v-if="formFields['PHP_VERSION']"
>
<el-select
v-model="runtime.params['PHP_VERSION']"
filterable
default-first-option
@change="changePHPVersion(runtime.params['PHP_VERSION'])"
>
<el-option
v-for="service in formFields['PHP_VERSION'].values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('runtime.image')" prop="image">
<el-input v-model="runtime.image"></el-input>
</el-form-item>
<el-form-item :label="$t('runtime.source')" prop="source">
<el-select v-model="runtime.source" filterable allow-create default-first-option>
<el-form-item
:label="getLabel(formFields['CONTAINER_PACKAGE_URL'])"
:rules="rules.params.CONTAINER_PACKAGE_URL"
v-if="formFields['CONTAINER_PACKAGE_URL']"
>
<el-select v-model="runtime.source" filterable default-first-option>
<el-option
v-for="(source, index) in phpSources"
:key="index"
:label="source.label + ' [' + source.value + ']'"
:value="source.value"
v-for="service in formFields['CONTAINER_PACKAGE_URL'].values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
<span class="input-help">
{{ $t('runtime.phpsourceHelper') }}
</span>
</el-form-item>
<el-form-item
:label="getLabel(formFields['PANEL_APP_PORT_HTTP'])"
prop="params.PANEL_APP_PORT_HTTP"
v-if="formFields['PANEL_APP_PORT_HTTP']"
>
<el-input
v-model.number="runtime.params['PANEL_APP_PORT_HTTP']"
maxlength="15"
:disabled="mode == 'edit'"
></el-input>
</el-form-item>
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<el-form-item>
<el-alert type="warning" :closable="false">
<template #default>
<div>{{ $t('runtime.buildHelper') }}</div>
<div>
<span>{{ $t('runtime.extendHelper') }}</span>
<el-link
target="_blank"
type="primary"
href="https://1panel.cn/docs/user_manual/websites/php/#php_1"
>
{{ $t('php.toExtensionsList') }}
</el-link>
</div>
</template>
</el-alert>
</el-form-item>
<el-form-item :label="$t('php.extensions')">
<el-select v-model="extensions" @change="changePHPExtension()" clearable>
@ -95,34 +146,17 @@
></el-option>
</el-select>
</el-form-item>
<Params
v-if="mode === 'create'"
v-model:form="runtime.params"
v-model:params="appParams"
v-model:rules="rules"
></Params>
<EditParams
v-if="mode === 'edit'"
v-model:form="runtime.params"
v-model:params="editParams"
v-model:rules="rules"
></EditParams>
<el-form-item>
<el-alert :title="$t('runtime.buildHelper')" type="warning" :closable="false" />
</el-form-item>
<el-form-item>
<el-alert type="info" :closable="false">
<span>{{ $t('runtime.extendHelper') }}</span>
<el-link
target="_blank"
type="primary"
href="https://1panel.cn/docs/user_manual/websites/php/#php_1"
>
{{ $t('php.toExtensionsList') }}
</el-link>
<br />
</el-alert>
<el-form-item :label="getLabel(formFields['PHP_EXTENSIONS'])" v-if="formFields['PHP_EXTENSIONS']">
<el-select v-model="runtime.params['PHP_EXTENSIONS']" multiple allowCreate filterable>
<el-option
v-for="service in formFields['PHP_EXTENSIONS'].values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
</el-form-item>
<div v-if="mode == 'edit'">
<el-form-item>
<el-checkbox v-model="runtime.rebuild">
@ -138,7 +172,6 @@
</div>
</div>
</div>
</div>
<div v-else>
<el-form-item>
<el-alert :title="$t('runtime.localHelper')" type="info" :closable="false" />
@ -169,8 +202,6 @@ import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { reactive, ref } from 'vue';
import Params from '../param/index.vue';
import EditParams from '../edit/index.vue';
interface OperateRrops {
id?: number;
@ -186,7 +217,6 @@ const loading = ref(false);
const initParam = ref(false);
const mode = ref('create');
const appParams = ref<App.AppParams>();
const editParams = ref<App.InstallParams[]>();
const appVersions = ref<string[]>([]);
const phpExtensions = ref([]);
const appReq = reactive({
@ -205,6 +235,7 @@ const initData = (type: string) => ({
source: 'mirrors.ustc.edu.cn',
});
const extensions = ref();
const formFields = ref();
let runtime = reactive<Runtime.RuntimeCreate>(initData('php'));
@ -215,38 +246,22 @@ const rules = ref<any>({
version: [Rules.requiredInput, Rules.paramCommon],
image: [Rules.requiredInput, Rules.imageName],
source: [Rules.requiredSelect],
params: {
PANEL_APP_PORT_HTTP: [Rules.requiredInput, Rules.port],
PHP_VERSION: [Rules.requiredSelect],
CONTAINER_PACKAGE_URL: [Rules.requiredSelect],
CONTAINER_NAME: [Rules.containerName, Rules.requiredInput],
},
});
const phpSources = [
{
label: i18n.global.t('runtime.ustc'),
value: 'mirrors.ustc.edu.cn',
},
{
label: i18n.global.t('runtime.netease'),
value: 'mirrors.163.com',
},
{
label: i18n.global.t('runtime.aliyun'),
value: 'mirrors.aliyun.com',
},
{
label: i18n.global.t('runtime.tsinghua'),
value: 'mirrors.tuna.tsinghua.edu.cn',
},
{
label: i18n.global.t('runtime.xtomhk'),
value: 'mirrors.xtom.com.hk',
},
{
label: i18n.global.t('runtime.xtom'),
value: 'mirrors.xtom.com',
},
{
label: i18n.global.t('commons.table.default'),
value: 'dl-cdn.alpinelinux.org',
},
];
const getLabel = (row: App.FromField): string => {
const language = localStorage.getItem('lang') || 'zh';
if (language == 'zh' || language == 'tw') {
return row.labelZh;
} else {
return row.labelEn;
}
};
const em = defineEmits(['close', 'submit']);
@ -296,6 +311,10 @@ const changeApp = (appId: number) => {
}
};
const changePHPVersion = (version: string) => {
runtime.image = 'php:' + version;
};
const changeVersion = () => {
loading.value = true;
initParam.value = false;
@ -305,6 +324,15 @@ const changeVersion = () => {
runtime.appDetailID = res.data.id;
runtime.image = res.data.image + ':' + runtime.version;
appParams.value = res.data.params;
const fileds = res.data.params.formFields;
formFields.value = {};
for (const index in fileds) {
formFields.value[fileds[index]['envKey']] = fileds[index];
runtime.params[fileds[index]['envKey']] = fileds[index]['default'];
if (fileds[index]['envKey'] == 'PHP_VERSION') {
runtime.image = 'php:' + fileds[index]['default'];
}
}
initParam.value = true;
})
.finally(() => {
@ -367,7 +395,7 @@ const getRuntime = async (id: number) => {
name: data.name,
appDetailID: data.appDetailID,
image: data.image,
params: {},
params: data.params,
type: data.type,
resource: data.resource,
appID: data.appID,
@ -375,12 +403,15 @@ const getRuntime = async (id: number) => {
rebuild: true,
source: data.source,
});
editParams.value = data.appParams;
if (mode.value == 'create') {
searchApp(data.appID);
} else {
initParam.value = true;
const fileds = data.appParams;
const forms = {};
for (const index in fileds) {
forms[fileds[index].key] = fileds[index];
}
formFields.value = forms;
runtime.params['PHP_EXTENSIONS'] = runtime.params['PHP_EXTENSIONS'].split(',');
initParam.value = true;
} catch (error) {}
};

View File

@ -50,8 +50,15 @@
<span>{{ $t('runtime.' + toLowerCase(row.resource)) }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version">
<template #default="{ row }">{{ row.params['PHP_VERSION'] }}</template>
</el-table-column>
<el-table-column :label="$t('runtime.image')" prop="image" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('commons.table.port')" prop="port">
<template #default="{ row }">
{{ row.port }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<el-popover

View File

@ -1,14 +1,53 @@
<template>
<div v-for="(p, index) in paramObjs" :key="index">
<el-form-item :label="getLabel(p)" :prop="p.prop">
<el-input
v-model.trim="form[p.envKey]"
v-if="p.type == 'text'"
:type="p.type"
@change="updateParam"
:disabled="p.disabled"
></el-input>
<el-input
v-model.number="form[p.envKey]"
@blur="form[p.envKey] = Number(form[p.envKey])"
v-if="p.type == 'number'"
maxlength="15"
@change="updateParam"
:disabled="p.disabled"
></el-input>
<el-input
v-model.trim="form[p.envKey]"
v-if="p.type == 'password'"
:type="p.type"
show-password
clearable
@change="updateParam"
></el-input>
<el-select
class="p-w-200"
v-model="form[p.envKey]"
v-if="p.type == 'service'"
@change="changeService(form[p.envKey], p.services)"
>
<el-option
v-for="service in p.services"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
<span v-if="p.type === 'service' && p.services.length === 0" class="ml-1.5">
<el-link type="primary" :underline="false" @click="toPage(p.key)">
{{ $t('app.toInstall') }}
</el-link>
</span>
<el-select
v-model="form[p.envKey]"
v-if="p.type == 'select'"
:multiple="p.multiple"
:allowCreate="p.allowCreate"
filterable
allow-create
default-first-option
@change="updateParam"
>
<el-option
v-for="service in p.values"
@ -17,15 +56,64 @@
:label="service.label"
></el-option>
</el-select>
<el-row :gutter="10" v-if="p.type == 'apps'">
<el-col :span="12">
<el-form-item :prop="p.prop">
<el-select
v-model="form[p.envKey]"
@change="getServices(p.child.envKey, form[p.envKey], p)"
class="p-w-200"
>
<el-option
v-for="service in p.values"
:label="service.label"
:key="service.value"
:value="service.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :prop="p.childProp">
<el-select
v-model="form[p.child.envKey]"
v-if="p.child.type == 'service'"
@change="changeService(form[p.child.envKey], p.services)"
class="p-w-200"
>
<el-option
v-for="service in p.services"
:key="service.label"
:value="service.value"
:label="service.label"
>
<span>{{ service.label }}</span>
<span class="float-right" v-if="service.from != ''">
<el-tag v-if="service.from === 'local'">{{ $t('database.local') }}</el-tag>
<el-tag v-else type="success">{{ $t('database.remote') }}</el-tag>
</span>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col>
<span v-if="p.child.type === 'service' && p.services.length === 0">
<el-link type="primary" :underline="false" @click="toPage(form[p.envKey])">
{{ $t('app.toInstall') }}
</el-link>
</span>
</el-col>
</el-row>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import { App } from '@/api/interface/app';
import { Rules } from '@/global/form-rules';
import { getLanguage } from '@/utils/util';
<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { getRandomStr } from '@/utils/util';
import { GetAppService } from '@/api/modules/app';
import { Rules } from '@/global/form-rules';
import { App } from '@/api/interface/app';
import { getDBName } from '@/utils/util';
interface ParamObj extends App.FromField {
services: App.AppService[];
@ -34,6 +122,8 @@ interface ParamObj extends App.FromField {
childProp: string;
}
const emit = defineEmits(['update:form', 'update:rules']);
const props = defineProps({
form: {
type: Object,
@ -53,9 +143,13 @@ const props = defineProps({
return {};
},
},
propStart: {
type: String,
default: '',
},
});
let form = reactive({});
const form = reactive({});
let rules = reactive({});
const params = computed({
get() {
@ -63,37 +157,103 @@ const params = computed({
},
set() {},
});
const emit = defineEmits(['update:form', 'update:rules']);
const propStart = computed({
get() {
return props.propStart;
},
set() {},
});
const paramObjs = ref<ParamObj[]>([]);
const updateParam = () => {
emit('update:form', form);
};
const paramObjs = ref<ParamObj[]>([]);
const handleParams = () => {
rules = props.rules;
if (params.value != undefined && params.value.formFields != undefined) {
for (const p of params.value.formFields) {
const pObj = p;
pObj.prop = p.envKey;
pObj.prop = propStart.value + p.envKey;
pObj.disabled = p.disabled;
form[p.envKey] = '';
paramObjs.value.push(pObj);
if (p.random) {
if (p.envKey === 'PANEL_DB_NAME') {
form[p.envKey] = p.default + '_' + getDBName(6);
} else {
form[p.envKey] = p.default + '_' + getRandomStr(6);
}
} else {
form[p.envKey] = p.default;
}
if (p.required) {
if (p.type === 'select') {
if (p.type === 'service' || p.type === 'apps') {
rules[p.envKey] = [Rules.requiredSelect];
if (p.child) {
p.childProp = propStart.value + p.child.envKey;
if (p.child.type === 'service') {
rules[p.child.envKey] = [Rules.requiredSelect];
}
}
} else {
rules[p.envKey] = [Rules.requiredInput];
}
if (p.rule && p.rule != '') {
rules[p.envKey].push(Rules[p.rule]);
}
form[p.envKey] = p.default;
} else {
delete rules[p.envKey];
}
if (p.type === 'apps') {
getServices(p.child.envKey, p.default, p);
p.child.services = [];
form[p.child.envKey] = '';
}
if (p.type === 'service') {
getServices(p.envKey, p.key, p);
p.services = [];
form[p.envKey] = '';
}
emit('update:rules', rules);
updateParam();
}
}
};
const getServices = async (childKey: string, key: string | undefined, pObj: ParamObj | undefined) => {
pObj.services = [];
await GetAppService(key).then((res) => {
pObj.services = res.data || [];
form[childKey] = '';
if (res.data && res.data.length > 0) {
form[childKey] = res.data[0].value;
if (pObj.params) {
pObj.params.forEach((param: App.FromParam) => {
if (param.key === key) {
form[param.envKey] = param.value;
}
});
}
changeService(form[childKey], pObj.services);
}
});
};
const changeService = (value: string, services: App.AppService[]) => {
services.forEach((item) => {
if (item.value === value && item.config) {
Object.entries(item.config).forEach(([k, v]) => {
if (form.hasOwnProperty(k)) {
form[k] = v;
}
});
}
});
updateParam();
};
const getLabel = (row: ParamObj): string => {
const language = getLanguage();
const language = localStorage.getItem('lang') || 'zh';
if (language == 'zh' || language == 'tw') {
return row.labelZh;
} else {
@ -101,6 +261,10 @@ const getLabel = (row: ParamObj): string => {
}
};
const toPage = (key: string) => {
window.location.href = '/apps/all?install=' + key;
};
onMounted(() => {
handleParams();
});

View File

@ -224,39 +224,19 @@
</el-form-item>
</el-col>
</el-row>
<div v-if="website.runtimeType === 'php'">
<Params
v-if="runtimeResource === 'appstore'"
:key="paramKey"
v-model:form="website.appinstall.params"
v-model:rules="rules.appinstall.params"
:params="appParams"
:propStart="'appinstall.params.'"
></Params>
<div v-else>
<div v-if="website.runtimeType === 'php' && runtimeResource === 'local'">
<el-form-item :label="$t('website.proxyType')" prop="proxyType">
<el-select v-model="website.proxyType">
<el-option :label="$t('website.tcp')" :value="'tcp'"></el-option>
<el-option :label="$t('website.unix')" :value="'unix'"></el-option>
</el-select>
</el-form-item>
<el-form-item
v-if="website.proxyType === 'tcp'"
:label="$t('commons.table.port')"
prop="port"
>
<el-form-item v-if="website.proxyType === 'tcp'" :label="$t('commons.table.port')" prop="port">
<el-input v-model.number="website.port"></el-input>
</el-form-item>
</div>
</div>
</div>
<el-form-item
prop="advanced"
v-if="
(website.type === 'runtime' && website.runtimeType === 'php') ||
(website.type === 'deployment' && website.appType === 'new')
"
>
<el-form-item prop="advanced" v-if="website.type === 'deployment' && website.appType === 'new'">
<el-checkbox v-model="website.appinstall.advanced" :label="$t('app.advanced')" size="large" />
</el-form-item>
@ -665,7 +645,7 @@ const runtimeResource = ref('appstore');
const runtimeReq = ref<Runtime.RuntimeReq>({
page: 1,
pageSize: 100,
status: 'normal',
status: 'running',
});
const runtimes = ref<Runtime.RuntimeDTO[]>([]);
const versionExist = ref(true);
@ -791,12 +771,7 @@ const getAppDetailByID = (id: number) => {
const changeRuntimeType = () => {
runtimeReq.value.type = website.value.runtimeType;
if (website.value.runtimeType == 'php') {
runtimeReq.value.status = 'normal';
} else {
runtimeReq.value.status = 'running';
website.value.appinstall.advanced = false;
}
website.value.runtimeID = undefined;
getRuntimes();
};

View File

@ -14,18 +14,11 @@
{{ $t('website.forceDeleteHelper') }}
</span>
</el-form-item>
<el-form-item v-if="type === 'deployment' || runtimeApp">
<el-checkbox
v-model="deleteReq.deleteApp"
:disabled="runtimeApp"
:label="$t('website.deleteApp')"
/>
<el-form-item v-if="type === 'deployment'">
<el-checkbox v-model="deleteReq.deleteApp" :label="$t('website.deleteApp')" />
<span class="input-help">
{{ $t('website.deleteAppHelper') }}
</span>
<span class="input-help text-red-500" v-if="runtimeApp">
{{ $t('website.deleteRuntimeHelper') }}
</span>
</el-form-item>
<el-form-item>
@ -79,7 +72,6 @@ const deleteForm = ref<FormInstance>();
const deleteInfo = ref('');
const websiteName = ref('');
const deleteHelper = ref('');
const runtimeApp = ref(false);
const subSites = ref('');
const handleClose = () => {
@ -95,10 +87,6 @@ const acceptParams = async (website: Website.WebsiteDTO) => {
forceDelete: false,
};
subSites.value = '';
if (website.type === 'runtime' && website.appInstallId > 0) {
runtimeApp.value = true;
deleteReq.value.deleteApp = true;
}
if (website.childSites && website.childSites.length > 0) {
subSites.value = website.childSites.join(',');
}

View File

@ -18,7 +18,7 @@
@is-exist="checkExist"
></AppStatus>
</template>
<template v-if="!openNginxConfig" #leftToolBar>
<template v-if="!openNginxConfig && nginxIsExist" #leftToolBar>
<el-button type="primary" @click="openCreate" :disabled="nginxStatus != 'Running'">
{{ $t('website.create') }}
</el-button>
@ -32,7 +32,7 @@
{{ $t('website.defaultHtml') }}
</el-button>
</template>
<template v-if="!openNginxConfig" #rightToolBar>
<template v-if="!openNginxConfig && nginxIsExist" #rightToolBar>
<el-select
v-model="req.websiteGroupId"
@change="search()"
@ -206,7 +206,13 @@
/>
</ComplexTable>
<el-card width="30%" v-if="nginxStatus != 'Running' && maskShow" class="mask-prompt">
<span>{{ $t('commons.service.serviceNotStarted', ['OpenResty']) }}</span>
<span v-if="nginxIsExist">{{ $t('commons.service.serviceNotStarted', ['OpenResty']) }}</span>
<span v-else>
{{ $t('app.checkInstalledWarn', ['OpenResty']) }}
<el-button @click="goRouter('openresty')" link icon="Position" type="primary">
{{ $t('database.goInstall') }}
</el-button>
</span>
</el-card>
</template>
</LayoutContent>
@ -302,6 +308,10 @@ const mobile = computed(() => {
return globalStore.isMobile();
});
const goRouter = async (key: string) => {
router.push({ name: 'AppAll', query: { install: key } });
};
const changeSort = ({ prop, order }) => {
if (order) {
switch (prop) {