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:
parent
42f904e25a
commit
2538944701
@ -160,3 +160,9 @@ type DelAppLink struct {
|
||||
Install *model.AppInstall
|
||||
ForceDelete bool
|
||||
}
|
||||
|
||||
type PHPForm struct {
|
||||
AdditionalProperties struct {
|
||||
FormFields []interface{} `yaml:"formFields"`
|
||||
} `yaml:"additionalProperties"`
|
||||
}
|
||||
|
@ -9,26 +9,27 @@ import (
|
||||
|
||||
type App struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Key string `json:"key" gorm:"not null;"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type" gorm:"not null"`
|
||||
Status string `json:"status" gorm:"not null"`
|
||||
Required string `json:"required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" gorm:"not null"`
|
||||
Website string `json:"website" gorm:"not null"`
|
||||
Github string `json:"github" gorm:"not null"`
|
||||
Document string `json:"document" gorm:"not null"`
|
||||
Recommend int `json:"recommend" gorm:"not null"`
|
||||
Resource string `json:"resource" gorm:"not null;default:remote"`
|
||||
ReadMe string `json:"readMe"`
|
||||
LastModified int `json:"lastModified"`
|
||||
Architectures string `json:"architectures"`
|
||||
MemoryRequired int `json:"memoryRequired"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Key string `json:"key" gorm:"not null;"`
|
||||
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type" gorm:"not null"`
|
||||
Status string `json:"status" gorm:"not null"`
|
||||
Required string `json:"required"`
|
||||
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
|
||||
Limit int `json:"limit" gorm:"not null"`
|
||||
Website string `json:"website" gorm:"not null"`
|
||||
Github string `json:"github" gorm:"not null"`
|
||||
Document string `json:"document" gorm:"not null"`
|
||||
Recommend int `json:"recommend" gorm:"not null"`
|
||||
Resource string `json:"resource" gorm:"not null;default:remote"`
|
||||
ReadMe string `json:"readMe"`
|
||||
LastModified int `json:"lastModified"`
|
||||
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:"-"`
|
||||
|
@ -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{})
|
||||
|
@ -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, ¶mMap); err != nil {
|
||||
if err = yaml.Unmarshal(param, ¶mMap); 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 {
|
||||
|
@ -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
|
||||
|
@ -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" {
|
||||
|
@ -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))
|
||||
|
@ -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,10 +100,10 @@ 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
|
||||
}
|
||||
}
|
||||
if containerName, ok := create.Params["CONTAINER_NAME"]; ok {
|
||||
if err := checkContainerName(containerName.(string)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
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 {
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
RuntimeStopped = "stopped"
|
||||
RuntimeUnhealthy = "unhealthy"
|
||||
RuntimeCreating = "creating"
|
||||
RuntimeStartErr = "startErr"
|
||||
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
|
@ -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"
|
||||
|
@ -71,7 +71,7 @@ MoveSiteDir: "當前升級需要遷移 OpenResty 網站目錄"
|
||||
MoveSiteToDir: "遷移網站目錄到 {{ .name }}"
|
||||
ErrMoveSiteDir: "遷移網站目錄失敗"
|
||||
MoveSiteDirSuccess: "遷移網站目錄成功"
|
||||
|
||||
DeleteRuntimePHP: "刪除運行環境 PHP 版本"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
|
@ -70,6 +70,7 @@ MoveSiteDir: "当前升级需要迁移 OpenResty 网站目录"
|
||||
MoveSiteToDir: "迁移网站目录到 {{ .name }}"
|
||||
ErrMoveSiteDir: "迁移网站目录失败"
|
||||
MoveSiteDirSuccess: "迁移网站目录成功"
|
||||
DeleteRuntimePHP: "删除 PHP 运行环境"
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
|
@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func Init() {
|
||||
go syncApp()
|
||||
//TODO 国际化处理
|
||||
//go syncApp()
|
||||
go syncInstalledApp()
|
||||
go syncRuntime()
|
||||
go syncSSL()
|
||||
|
@ -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)
|
||||
|
@ -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{})
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -77,6 +77,7 @@ export namespace App {
|
||||
child?: FromFieldChild;
|
||||
params?: FromParam[];
|
||||
multiple?: boolean;
|
||||
allowCreate?: boolean;
|
||||
}
|
||||
|
||||
export interface FromFieldChild extends FromField {
|
||||
|
@ -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',
|
||||
|
@ -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: '編輯運行環境',
|
||||
|
@ -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: '编辑运行环境',
|
||||
|
@ -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"
|
||||
|
@ -68,74 +68,107 @@
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<div v-if="initParam">
|
||||
<div v-if="runtime.type === 'php'">
|
||||
<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-option
|
||||
v-for="(source, index) in phpSources"
|
||||
:key="index"
|
||||
:label="source.label + ' [' + source.value + ']'"
|
||||
:value="source.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<span class="input-help">
|
||||
{{ $t('runtime.phpsourceHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('php.extensions')">
|
||||
<el-select v-model="extensions" @change="changePHPExtension()" clearable>
|
||||
<el-option
|
||||
v-for="(extension, index) in phpExtensions"
|
||||
:key="index"
|
||||
:label="extension.name"
|
||||
:value="extension.extensions"
|
||||
></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
|
||||
: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="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="service in formFields['CONTAINER_PACKAGE_URL'].values"
|
||||
:key="service.label"
|
||||
:value="service.value"
|
||||
:label="service.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</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>
|
||||
<el-option
|
||||
v-for="(extension, index) in phpExtensions"
|
||||
:key="index"
|
||||
:label="extension.name"
|
||||
:value="extension.extensions"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<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-alert :title="$t('runtime.buildHelper')" type="warning" :closable="false" />
|
||||
<el-checkbox v-model="runtime.rebuild">
|
||||
{{ $t('runtime.rebuild') }}
|
||||
</el-checkbox>
|
||||
</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>
|
||||
<span>{{ $t('runtime.rebuildHelper') }}</span>
|
||||
<br />
|
||||
</el-alert>
|
||||
</el-form-item>
|
||||
<div v-if="mode == 'edit'">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="runtime.rebuild">
|
||||
{{ $t('runtime.rebuild') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-alert type="info" :closable="false">
|
||||
<span>{{ $t('runtime.rebuildHelper') }}</span>
|
||||
<br />
|
||||
</el-alert>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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) {}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
}
|
||||
} else {
|
||||
delete rules[p.envKey];
|
||||
}
|
||||
form[p.envKey] = p.default;
|
||||
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();
|
||||
}
|
||||
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();
|
||||
});
|
||||
|
@ -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>
|
||||
<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-input v-model.number="website.port"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<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-input v-model.number="website.port"></el-input>
|
||||
</el-form-item>
|
||||
</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.appinstall.advanced = false;
|
||||
website.value.runtimeID = undefined;
|
||||
getRuntimes();
|
||||
};
|
||||
|
@ -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(',');
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user