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

feat: 优化应用升级逻辑 (#1075)

This commit is contained in:
zhengkunwang223 2023-05-18 16:48:19 +08:00 committed by GitHub
parent 72bc99bddc
commit d20d5946f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 370 additions and 201 deletions

View File

@ -61,12 +61,7 @@ type AppInstalledOperate struct {
type AppInstalledUpdate struct { type AppInstalledUpdate struct {
InstallId uint `json:"installId" validate:"required"` InstallId uint `json:"installId" validate:"required"`
Params map[string]interface{} `json:"params" validate:"required"` Params map[string]interface{} `json:"params" validate:"required"`
Advanced bool `json:"advanced"` AppContainerConfig
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
} }
type PortUpdate struct { type PortUpdate struct {

View File

@ -2,6 +2,7 @@ package response
import ( import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
@ -85,9 +86,5 @@ type AppParam struct {
type AppConfig struct { type AppConfig struct {
Params []AppParam `json:"params"` Params []AppParam `json:"params"`
CpuQuota float64 `json:"cpuQuota"` request.AppContainerConfig
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
} }

View File

@ -5,25 +5,23 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"io"
"net/http"
"os"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo" "github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io"
"net/http"
"os"
"path"
"strconv"
) )
type AppService struct { type AppService struct {
@ -301,49 +299,10 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
servicesMap[v] = servicesMap[k] servicesMap[v] = servicesMap[k]
delete(servicesMap, k) delete(servicesMap, k)
} }
serviceValue := servicesMap[appInstall.ServiceName].(map[string]interface{})
if req.Advanced && (req.CpuQuota > 0 || req.MemoryLimit > 0) {
deploy := map[string]interface{}{
"resources": map[string]interface{}{
"limits": map[string]interface{}{
"cpus": "${CPUS}",
"memory": "${MEMORY_LIMIT}",
},
},
}
req.Params[constant.CPUS] = "0"
if req.CpuQuota > 0 {
req.Params[constant.CPUS] = req.CpuQuota
}
req.Params[constant.MemoryLimit] = "0"
if req.MemoryLimit > 0 {
req.Params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit
}
serviceValue["deploy"] = deploy
}
ports, ok := serviceValue["ports"].([]interface{}) if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
if ok { return
allowHost := "127.0.0.1"
if req.AllowPort {
allowHost = "0.0.0.0"
} }
req.Params[constant.HostIP] = allowHost
for i, port := range ports {
portStr, portOK := port.(string)
if !portOK {
continue
}
portArray := strings.Split(portStr, ":")
if len(portArray) == 2 {
portArray = append([]string{"${HOST_IP}"}, portArray...)
}
ports[i] = strings.Join(portArray, ":")
}
serviceValue["ports"] = ports
}
servicesMap[appInstall.ServiceName] = serviceValue
var ( var (
composeByte []byte composeByte []byte
@ -379,14 +338,17 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
return return
} }
go func() { go func() {
if err = downloadApp(app, appDetail, appInstall, req); err != nil { if err = copyData(app, appDetail, appInstall, req); err != nil {
if appInstall.Status == constant.Installing { if appInstall.Status == constant.Installing {
appInstall.Status = constant.Error appInstall.Status = constant.Error
appInstall.Message = err.Error() appInstall.Message = err.Error()
} }
_ = appInstallRepo.Save(ctx, appInstall) _ = appInstallRepo.Save(context.Background(), appInstall)
return return
} }
go func() {
_, _ = http.Get(appDetail.DownloadCallBackUrl)
}()
upApp(appInstall) upApp(appInstall)
}() }()
go updateToolApp(appInstall) go updateToolApp(appInstall)

View File

@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3"
"math" "math"
"os" "os"
"path" "path"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -262,23 +262,17 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
} }
} }
backupDockerCompose := installed.DockerCompose
if req.Advanced { if req.Advanced {
if req.ContainerName != "" { composeMap := make(map[string]interface{})
req.Params[constant.ContainerName] = req.ContainerName if err := addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
return err
} }
req.Params[constant.CPUS] = "0" composeByte, err := yaml.Marshal(composeMap)
if req.CpuQuota > 0 { if err != nil {
req.Params[constant.CPUS] = req.CpuQuota return err
} }
req.Params[constant.MemoryLimit] = "0" installed.DockerCompose = string(composeByte)
if req.MemoryLimit > 0 {
req.Params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit
}
allowHost := "127.0.0.1"
if req.AllowPort {
allowHost = "0.0.0.0"
}
req.Params[constant.HostIP] = allowHost
} }
envPath := path.Join(installed.GetPath(), ".env") envPath := path.Join(installed.GetPath(), ".env")
@ -286,6 +280,7 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
if err != nil { if err != nil {
return err return err
} }
backupEnvMaps := oldEnvMaps
handleMap(req.Params, oldEnvMaps) handleMap(req.Params, oldEnvMaps)
paramByte, err := json.Marshal(oldEnvMaps) paramByte, err := json.Marshal(oldEnvMaps)
if err != nil { if err != nil {
@ -295,36 +290,42 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
if err := env.Write(oldEnvMaps, envPath); err != nil { if err := env.Write(oldEnvMaps, envPath); err != nil {
return err return err
} }
_ = appInstallRepo.Save(context.Background(), &installed) fileOp := files.NewFileOp()
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), 0755)
if err := rebuildApp(installed); err != nil { if err := rebuildApp(installed); err != nil {
_ = env.Write(backupEnvMaps, envPath)
_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), 0755)
return err return err
} }
installed.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &installed)
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID)) website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID))
if changePort && website.ID != 0 && website.Status == constant.Running { if changePort && website.ID != 0 && website.Status == constant.Running {
go func() {
nginxInstall, err := getNginxFull(&website) nginxInstall, err := getNginxFull(&website)
if err != nil { if err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err) global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
} }
config := nginxInstall.SiteConfig.Config config := nginxInstall.SiteConfig.Config
servers := config.FindServers() servers := config.FindServers()
if len(servers) == 0 { if len(servers) == 0 {
return buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")) global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")).Error())
return
} }
server := servers[0] server := servers[0]
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort) proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
server.UpdateRootProxy([]string{proxy}) server.UpdateRootProxy([]string{proxy})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err) global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
} }
if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil { if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err) global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
} }
}
if changePort {
go func() {
_ = OperateFirewallPort(oldPorts, newPorts)
}() }()
} }
return nil return nil
@ -543,31 +544,10 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
params = append(params, appParam) params = append(params, appParam)
} }
} }
res.ContainerName = envs[constant.ContainerName].(string)
res.AllowPort = envs[constant.HostIP].(string) == "0.0.0.0"
numStr, ok := envs[constant.CPUS].(string)
if ok {
num, err := strconv.ParseFloat(numStr, 64)
if err == nil {
res.CpuQuota = num
}
}
num64, ok := envs[constant.CPUS].(float64)
if ok {
res.CpuQuota = num64
}
re := regexp.MustCompile(`(\d+(?:\.\d+)?)\s*([KMGT]?B)`) config := getAppCommonConfig(envs)
matches := re.FindStringSubmatch(envs[constant.MemoryLimit].(string))
if len(matches) == 3 {
num, err := strconv.ParseFloat(matches[1], 64)
if err == nil {
unit := matches[2]
res.MemoryLimit = num
res.MemoryUnit = unit
}
}
res.Params = params res.Params = params
res.AppContainerConfig = config
return &res, nil return &res, nil
} }

View File

@ -8,11 +8,14 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv" "github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"math" "math"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -226,40 +229,110 @@ func upgradeInstall(installId uint, detailId uint) error {
return err return err
} }
install.Status = constant.Upgrading
go func() {
var upErr error
defer func() {
if upErr != nil {
install.Status = constant.UpgradeErr
install.Message = err.Error()
_ = appInstallRepo.Save(context.Background(), &install)
}
}()
if upErr = downloadApp(install.App, detail, &install); upErr != nil {
return
}
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version) detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if install.App.Resource == constant.AppResourceLocal { if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "localApps", strings.TrimPrefix(install.App.Key, "local"), "versions", detail.Version) detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
} }
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath())) cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput() stdout, err := cmd.CombinedOutput()
if err != nil { if err != nil {
if stdout != nil { if stdout != nil {
return errors.New(string(stdout)) upErr = errors.New(string(stdout))
return
} }
return err upErr = err
return
} }
if out, err := compose.Down(install.GetComposePath()); err != nil { composeMap := make(map[string]interface{})
if out != "" { if upErr = yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); upErr != nil {
return errors.New(out) return
} }
return err value, ok := composeMap["services"]
if !ok {
upErr = buserr.New(constant.ErrFileParse)
return
} }
install.DockerCompose = detail.DockerCompose servicesMap := value.(map[string]interface{})
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
}
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
delete(servicesMap, oldServiceName)
envs := make(map[string]interface{})
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
return
}
config := getAppCommonConfig(envs)
config.Advanced = true
if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil {
return
}
paramByte, upErr := json.Marshal(envs)
if upErr != nil {
return
}
install.Env = string(paramByte)
composeByte, upErr := yaml.Marshal(composeMap)
if upErr != nil {
return
}
install.DockerCompose = string(composeByte)
install.Version = detail.Version install.Version = detail.Version
install.AppDetailId = detailId install.AppDetailId = detailId
go func() {
_, _ = http.Get(detail.DownloadCallBackUrl)
}()
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
return
}
return
}
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil { if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
return err return
} }
if out, err := compose.Up(install.GetComposePath()); err != nil { if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" { if out != "" {
return errors.New(out) upErr = errors.New(out)
return
} }
return err upErr = err
return
} }
install.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &install)
}()
return appInstallRepo.Save(context.Background(), &install) return appInstallRepo.Save(context.Background(), &install)
} }
@ -332,11 +405,12 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
} }
} }
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) { func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall) (err error) {
fileOp := files.NewFileOp()
appResourceDir := path.Join(constant.AppResourceDir, app.Resource) appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if !appDetail.Update {
if app.Resource == constant.AppResourceRemote && appDetail.Update { return
}
fileOp := files.NewFileOp()
appDownloadDir := path.Join(appResourceDir, app.Key) appDownloadDir := path.Join(appResourceDir, app.Key)
if !fileOp.Stat(appDownloadDir) { if !fileOp.Stat(appDownloadDir) {
_ = fileOp.CreateDir(appDownloadDir, 0755) _ = fileOp.CreateDir(appDownloadDir, 0755)
@ -347,17 +421,35 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
} }
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl) global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz") filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
defer func() {
if err != nil {
appInstall.Status = constant.DownloadErr appInstall.Status = constant.DownloadErr
appInstall.Message = err.Error()
}
}()
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err) global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return return
} }
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil { if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err) global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
appInstall.Status = constant.DownloadErr
return return
} }
_ = fileOp.DeleteFile(filePath) _ = fileOp.DeleteFile(filePath)
return
}
func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
fileOp := files.NewFileOp()
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if app.Resource == constant.AppResourceRemote {
err = downloadApp(app, appDetail, appInstall)
if err != nil {
return
}
} }
appKey := app.Key appKey := app.Key
installAppDir := path.Join(constant.AppInstallDir, app.Key) installAppDir := path.Join(constant.AppInstallDir, app.Key)
@ -639,3 +731,108 @@ func updateToolApp(installed *model.AppInstall) {
return return
} }
} }
func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}) error {
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
return buserr.New(constant.ErrFileParse)
}
service, serviceExist := services[serviceName]
if !serviceExist {
return buserr.New(constant.ErrFileParse)
}
serviceValue := service.(map[string]interface{})
deploy := map[string]interface{}{
"resources": map[string]interface{}{
"limits": map[string]interface{}{
"cpus": "${CPUS}",
"memory": "${MEMORY_LIMIT}",
},
},
}
serviceValue["deploy"] = deploy
ports, ok := serviceValue["ports"].([]interface{})
if ok {
for i, port := range ports {
portStr, portOK := port.(string)
if !portOK {
continue
}
portArray := strings.Split(portStr, ":")
if len(portArray) == 2 {
portArray = append([]string{"${HOST_IP}"}, portArray...)
}
ports[i] = strings.Join(portArray, ":")
}
serviceValue["ports"] = ports
}
params[constant.CPUS] = "0"
params[constant.MemoryLimit] = "0"
if req.Advanced {
if req.CpuQuota > 0 {
params[constant.CPUS] = req.CpuQuota
}
if req.MemoryLimit > 0 {
params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit
}
}
_, portExist := serviceValue["ports"].([]interface{})
if portExist {
allowHost := "127.0.0.1"
if req.Advanced && req.AllowPort {
allowHost = "0.0.0.0"
}
params[constant.HostIP] = allowHost
}
services[serviceName] = serviceValue
return nil
}
func getAppCommonConfig(envs map[string]interface{}) request.AppContainerConfig {
config := request.AppContainerConfig{}
if hostIp, ok := envs[constant.HostIP]; ok {
config.AllowPort = hostIp.(string) == "0.0.0.0"
} else {
config.AllowPort = true
}
if cpuCore, ok := envs[constant.CPUS]; ok {
numStr, ok := cpuCore.(string)
if ok {
num, err := strconv.ParseFloat(numStr, 64)
if err == nil {
config.CpuQuota = num
}
} else {
num64, flOk := cpuCore.(float64)
if flOk {
config.CpuQuota = num64
}
}
} else {
config.CpuQuota = 0
}
if memLimit, ok := envs[constant.MemoryLimit]; ok {
re := regexp.MustCompile(`(\d+(?:\.\d+)?)\s*([KMGT]?B)`)
matches := re.FindStringSubmatch(memLimit.(string))
if len(matches) == 3 {
num, err := strconv.ParseFloat(matches[1], 64)
if err == nil {
unit := matches[2]
config.MemoryLimit = num
config.MemoryUnit = unit
}
}
} else {
config.MemoryLimit = 0
config.MemoryUnit = "M"
}
if containerName, ok := envs[constant.ContainerName]; ok {
config.ContainerName = containerName.(string)
}
return config
}

View File

@ -9,6 +9,8 @@ const (
Syncing = "Syncing" Syncing = "Syncing"
DownloadErr = "DownloadErr" DownloadErr = "DownloadErr"
DirNotFound = "DirNotFound" DirNotFound = "DirNotFound"
Upgrading = "Upgrading"
UpgradeErr = "UpgradeErr"
ContainerPrefix = "1Panel-" ContainerPrefix = "1Panel-"

View File

@ -5,31 +5,31 @@ import (
) )
func Up(filePath string) (string, error) { 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 return stdout, err
} }
func Down(filePath string) (string, error) { 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 return stdout, err
} }
func Start(filePath string) (string, error) { 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 return stdout, err
} }
func Stop(filePath string) (string, error) { 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 return stdout, err
} }
func Restart(filePath string) (string, error) { 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 return stdout, err
} }
func Operate(filePath, operation string) (string, error) { 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 return stdout, err
} }

View File

@ -477,12 +477,16 @@ func (f FileOp) Compress(srcRiles []string, dst string, name string, cType Compr
return nil return nil
} }
func isIgnoreFile(name string) bool {
return strings.HasPrefix(name, "__MACOSX") || strings.HasSuffix(name, ".DS_Store") || strings.HasPrefix(name, "._")
}
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error { func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error {
format := getFormat(cType) format := getFormat(cType)
handler := func(ctx context.Context, archFile archiver.File) error { handler := func(ctx context.Context, archFile archiver.File) error {
info := archFile.FileInfo info := archFile.FileInfo
if strings.HasPrefix(archFile.NameInArchive, "__MACOSX") { if isIgnoreFile(archFile.Name()) {
return nil return nil
} }
filePath := filepath.Join(dst, archFile.NameInArchive) filePath := filepath.Join(dst, archFile.NameInArchive)

View File

@ -19,11 +19,12 @@ const props = defineProps({
let status = ref('running'); let status = ref('running');
const getType = (status: string) => { const getType = (status: string) => {
if (status.includes('error') || status.includes('err')) {
return 'danger';
}
switch (status) { switch (status) {
case 'running': case 'running':
return 'success'; return 'success';
case 'error':
return 'danger';
case 'stopped': case 'stopped':
return 'danger'; return 'danger';
default: default:
@ -31,7 +32,7 @@ const getType = (status: string) => {
} }
}; };
const loadingStatus = ['installing', 'building', 'restarting']; const loadingStatus = ['installing', 'building', 'restarting', 'upgrading'];
const loadingIcon = (status: string): boolean => { const loadingIcon = (status: string): boolean => {
return loadingStatus.indexOf(status) > -1; return loadingStatus.indexOf(status) > -1;

View File

@ -196,6 +196,8 @@ const message = {
disabled: 'Disabled', disabled: 'Disabled',
normal: 'Normal', normal: 'Normal',
building: 'Building', building: 'Building',
downloaderr: 'Download Error',
upgrading: 'Upgrading',
}, },
}, },
menu: { menu: {
@ -1091,6 +1093,7 @@ const message = {
'Allowing external port access will release the firewall port, please do not release the php operating environment', 'Allowing external port access will release the firewall port, please do not release the php operating environment',
appInstallWarn: appInstallWarn:
'The application does not release the external access port by default, you can choose to release it in the advanced settings', 'The application does not release the external access port by default, you can choose to release it in the advanced settings',
upgradeStart: 'Start upgrading! Please refresh the page later',
}, },
website: { website: {
website: 'Website', website: 'Website',

View File

@ -201,6 +201,8 @@ const message = {
disabled: '已停止', disabled: '已停止',
normal: '正常', normal: '正常',
building: '制作镜像中', building: '制作镜像中',
downloaderr: '下载失败',
upgrading: '升级中',
}, },
units: { units: {
second: '秒', second: '秒',
@ -1089,6 +1091,7 @@ const message = {
allowPort: '端口外部访问', allowPort: '端口外部访问',
allowPortHelper: '允许外部端口访问会放开防火墙端口php运行环境请勿放开', allowPortHelper: '允许外部端口访问会放开防火墙端口php运行环境请勿放开',
appInstallWarn: '应用默认不放开外部访问端口可以在高级设置中选择放开', appInstallWarn: '应用默认不放开外部访问端口可以在高级设置中选择放开',
upgradeStart: '开始升级请稍后刷新页面',
}, },
website: { website: {
website: '网站', website: '网站',

View File

@ -24,6 +24,9 @@
float: right; float: right;
margin-right: 10px; margin-right: 10px;
} }
.msg {
margin-left: 10px;
}
} }
.d-description { .d-description {

View File

@ -179,7 +179,7 @@ const get = async () => {
} }
paramModel.value.memoryLimit = res.data.memoryLimit; paramModel.value.memoryLimit = res.data.memoryLimit;
paramModel.value.cpuQuota = res.data.cpuQuota; paramModel.value.cpuQuota = res.data.cpuQuota;
paramModel.value.memoryUnit = res.data.memoryUnit; paramModel.value.memoryUnit = res.data.memoryUnit !== '' ? res.data.memoryUnit : 'MB';
paramModel.value.allowPort = res.data.allowPort; paramModel.value.allowPort = res.data.allowPort;
paramModel.value.containerName = res.data.containerName; paramModel.value.containerName = res.data.containerName;
paramModel.value.advanced = false; paramModel.value.advanced = false;

View File

@ -81,25 +81,25 @@
<el-col :xs="21" :sm="21" :md="21" :lg="20" :xl="20"> <el-col :xs="21" :sm="21" :md="21" :lg="20" :xl="20">
<div class="a-detail"> <div class="a-detail">
<div class="d-name"> <div class="d-name">
<el-button link type="info">
<span class="name">{{ installed.name }}</span> <span class="name">{{ installed.name }}</span>
</el-button>
<span class="status"> <span class="status">
<Status :key="installed.status" :status="installed.status"></Status>
</span>
<span class="msg">
<el-popover <el-popover
v-if="installed.status === 'Error'" v-if="isAppErr(installed)"
placement="bottom" placement="bottom"
:width="400" :width="400"
trigger="hover" trigger="hover"
:content="installed.message" :content="installed.message"
> >
<template #reference> <template #reference>
<Status <el-button link type="primary">详情</el-button>
:key="installed.status"
:status="installed.status"
></Status>
</template> </template>
</el-popover> </el-popover>
<span v-else>
<Status :key="installed.status" :status="installed.status"></Status>
</span>
</span> </span>
<el-button <el-button
@ -132,6 +132,7 @@
plain plain
round round
size="small" size="small"
:disabled="installed.status === 'Upgrading'"
@click="openOperate(installed, 'upgrade')" @click="openOperate(installed, 'upgrade')"
v-if="mode === 'upgrade'" v-if="mode === 'upgrade'"
> >
@ -337,18 +338,27 @@ const buttons = [
click: (row: any) => { click: (row: any) => {
openOperate(row, 'sync'); openOperate(row, 'sync');
}, },
disabled: (row: any) => {
return row.status === 'DownloadErr' || row.status === 'Upgrading';
},
}, },
{ {
label: i18n.global.t('app.rebuild'), label: i18n.global.t('app.rebuild'),
click: (row: any) => { click: (row: any) => {
openOperate(row, 'rebuild'); openOperate(row, 'rebuild');
}, },
disabled: (row: any) => {
return row.status === 'DownloadErr' || row.status === 'Upgrading';
},
}, },
{ {
label: i18n.global.t('app.restart'), label: i18n.global.t('app.restart'),
click: (row: any) => { click: (row: any) => {
openOperate(row, 'restart'); openOperate(row, 'restart');
}, },
disabled: (row: any) => {
return row.status === 'DownloadErr' || row.status === 'Upgrading';
},
}, },
{ {
label: i18n.global.t('app.start'), label: i18n.global.t('app.start'),
@ -356,7 +366,12 @@ const buttons = [
openOperate(row, 'start'); openOperate(row, 'start');
}, },
disabled: (row: any) => { disabled: (row: any) => {
return row.status === 'Running' || row.status === 'Error'; return (
row.status === 'Running' ||
row.status === 'Error' ||
row.status === 'DownloadErr' ||
row.status === 'Upgrading'
);
}, },
}, },
{ {
@ -365,7 +380,7 @@ const buttons = [
openOperate(row, 'stop'); openOperate(row, 'stop');
}, },
disabled: (row: any) => { disabled: (row: any) => {
return row.status !== 'Running'; return row.status !== 'Running' || row.status === 'DownloadErr' || row.status === 'Upgrading';
}, },
}, },
{ {
@ -379,6 +394,9 @@ const buttons = [
click: (row: any) => { click: (row: any) => {
openParam(row.id); openParam(row.id);
}, },
disabled: (row: any) => {
return row.status === 'DownloadErr' || row.status === 'Upgrading';
},
}, },
]; ];
@ -404,6 +422,10 @@ const openParam = (installId: number) => {
appParamRef.value.acceptParams({ id: installId }); appParamRef.value.acceptParams({ id: installId });
}; };
const isAppErr = (row: any) => {
return row.status.includes('Err') || row.status.includes('Error');
};
onMounted(() => { onMounted(() => {
const path = router.currentRoute.value.path; const path = router.currentRoute.value.path;
if (path == '/apps/upgrade') { if (path == '/apps/upgrade') {

View File

@ -83,7 +83,7 @@ const operate = async () => {
loading.value = true; loading.value = true;
await InstalledOp(operateReq) await InstalledOp(operateReq)
.then(() => { .then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('app.upgradeStart'));
bus.emit('upgrade', true); bus.emit('upgrade', true);
handleClose(); handleClose();
}) })