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

This commit is contained in:
zhengkunwang223
2023-05-18 16:48:19 +08:00
committed by GitHub
parent 72bc99bddc
commit d20d5946f2
15 changed files with 370 additions and 201 deletions

View File

@@ -59,14 +59,9 @@ 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"
@@ -84,10 +85,6 @@ 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,50 +299,11 @@ 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
paramByte []byte paramByte []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 {
nginxInstall, err := getNginxFull(&website)
if err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
config := nginxInstall.SiteConfig.Config
servers := config.FindServers()
if len(servers) == 0 {
return buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid"))
}
server := servers[0]
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
server.UpdateRootProxy([]string{proxy})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
}
if changePort {
go func() { go func() {
_ = OperateFirewallPort(oldPorts, newPorts) nginxInstall, err := getNginxFull(&website)
if err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
config := nginxInstall.SiteConfig.Config
servers := config.FindServers()
if len(servers) == 0 {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")).Error())
return
}
server := servers[0]
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
server.UpdateRootProxy([]string{proxy})
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil {
global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error())
return
}
}() }()
} }
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
} }
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version) install.Status = constant.Upgrading
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "localApps", strings.TrimPrefix(install.App.Key, "local"), "versions", detail.Version)
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath())) go func() {
stdout, err := cmd.CombinedOutput() var upErr error
if err != nil { defer func() {
if stdout != nil { if upErr != nil {
return errors.New(string(stdout)) install.Status = constant.UpgradeErr
} install.Message = err.Error()
return err _ = appInstallRepo.Save(context.Background(), &install)
} }
}()
if out, err := compose.Down(install.GetComposePath()); err != nil { if upErr = downloadApp(install.App, detail, &install); upErr != nil {
if out != "" { return
return errors.New(out)
} }
return err
}
install.DockerCompose = detail.DockerCompose
install.Version = detail.Version
install.AppDetailId = detailId
fileOp := files.NewFileOp() detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil { if install.App.Resource == constant.AppResourceLocal {
return err detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
} }
return err
} cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput()
if err != nil {
if stdout != nil {
upErr = errors.New(string(stdout))
return
}
upErr = err
return
}
composeMap := make(map[string]interface{})
if upErr = yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); upErr != nil {
return
}
value, ok := composeMap["services"]
if !ok {
upErr = buserr.New(constant.ErrFileParse)
return
}
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.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()
if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
return
}
if out, err := compose.Up(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
return
}
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,32 +405,51 @@ 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) {
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if !appDetail.Update {
return
}
fileOp := files.NewFileOp()
appDownloadDir := path.Join(appResourceDir, app.Key)
if !fileOp.Stat(appDownloadDir) {
_ = fileOp.CreateDir(appDownloadDir, 0755)
}
appVersionDir := path.Join(appDownloadDir, appDetail.Version)
if !fileOp.Stat(appVersionDir) {
_ = fileOp.CreateDir(appVersionDir, 0755)
}
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
defer func() {
if err != nil {
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)
return
}
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
return
}
_ = fileOp.DeleteFile(filePath)
return
}
func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
appResourceDir := path.Join(constant.AppResourceDir, app.Resource) appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if app.Resource == constant.AppResourceRemote && appDetail.Update { if app.Resource == constant.AppResourceRemote {
appDownloadDir := path.Join(appResourceDir, app.Key) err = downloadApp(app, appDetail, appInstall)
if !fileOp.Stat(appDownloadDir) { if err != nil {
_ = fileOp.CreateDir(appDownloadDir, 0755)
}
appVersionDir := path.Join(appDownloadDir, appDetail.Version)
if !fileOp.Stat(appVersionDir) {
_ = fileOp.CreateDir(appVersionDir, 0755)
}
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
appInstall.Status = constant.DownloadErr
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return return
} }
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
appInstall.Status = constant.DownloadErr
return
}
_ = fileOp.DeleteFile(filePath)
} }
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">
<span class="name">{{ installed.name }}</span> <el-button link type="info">
<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();
}) })