mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 12:39:01 +08:00
feat: 应用升级支持对比 docker-compose 文件 (#5274)
Refs https://github.com/1Panel-dev/1Panel/issues/2826
This commit is contained in:
parent
0182586869
commit
8b781d466a
@ -205,14 +205,13 @@ func (b *BaseApi) GetServices(c *gin.Context) {
|
||||
// @Param appInstallId path integer true "request"
|
||||
// @Success 200 {array} dto.AppVersion
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/installed/:appInstallId/versions [get]
|
||||
// @Router /apps/installed/update/versions [post]
|
||||
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
|
||||
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
|
||||
var req request.AppUpdateVersion
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
versions, err := appInstallService.GetUpdateVersions(appInstallId)
|
||||
versions, err := appInstallService.GetUpdateVersions(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
@ -37,8 +37,9 @@ type AppOssConfig struct {
|
||||
}
|
||||
|
||||
type AppVersion struct {
|
||||
Version string `json:"version"`
|
||||
DetailId uint `json:"detailId"`
|
||||
Version string `json:"version"`
|
||||
DetailId uint `json:"detailId"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type AppList struct {
|
||||
|
@ -61,15 +61,24 @@ type AppBackupDelete struct {
|
||||
}
|
||||
|
||||
type AppInstalledOperate struct {
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
BackupId uint `json:"backupId"`
|
||||
DetailId uint `json:"detailId"`
|
||||
Operate constant.AppOperate `json:"operate" validate:"required"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
DeleteBackup bool `json:"deleteBackup"`
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
InstallId uint `json:"installId" validate:"required"`
|
||||
BackupId uint `json:"backupId"`
|
||||
DetailId uint `json:"detailId"`
|
||||
Operate constant.AppOperate `json:"operate" validate:"required"`
|
||||
ForceDelete bool `json:"forceDelete"`
|
||||
DeleteBackup bool `json:"deleteBackup"`
|
||||
DeleteDB bool `json:"deleteDB"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type AppInstallUpgrade struct {
|
||||
InstallID uint `json:"installId"`
|
||||
DetailID uint `json:"detailId"`
|
||||
Backup bool `json:"backup"`
|
||||
PullImage bool `json:"pullImage"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
@ -88,3 +97,8 @@ type PortUpdate struct {
|
||||
Name string `json:"name"`
|
||||
Port int64 `json:"port"`
|
||||
}
|
||||
|
||||
type AppUpdateVersion struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
UpdateVersion string `json:"updateVersion"`
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ type AppDTO struct {
|
||||
type AppDto struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"ID"`
|
||||
ID uint `json:"id"`
|
||||
ShortDescZh string `json:"shortDescZh"`
|
||||
ShortDescEn string `json:"shortDescEn"`
|
||||
Icon string `json:"icon"`
|
||||
@ -88,24 +88,25 @@ type AppInstalledDTO struct {
|
||||
}
|
||||
|
||||
type AppInstallDTO struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
Path string `json:"path"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
Icon string `json:"icon"`
|
||||
AppName string `json:"appName"`
|
||||
Ready int `json:"ready"`
|
||||
Total int `json:"total"`
|
||||
AppKey string `json:"appKey"`
|
||||
AppType string `json:"appType"`
|
||||
AppStatus string `json:"appStatus"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AppID uint `json:"appID"`
|
||||
AppDetailID uint `json:"appDetailID"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
HttpsPort int `json:"httpsPort"`
|
||||
Path string `json:"path"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
Icon string `json:"icon"`
|
||||
AppName string `json:"appName"`
|
||||
Ready int `json:"ready"`
|
||||
Total int `json:"total"`
|
||||
AppKey string `json:"appKey"`
|
||||
AppType string `json:"appType"`
|
||||
AppStatus string `json:"appStatus"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
}
|
||||
|
||||
type DatabaseConn struct {
|
||||
|
@ -5,11 +5,14 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
"github.com/docker/docker/api/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -49,7 +52,7 @@ type IAppInstallService interface {
|
||||
IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error
|
||||
SyncAll(systemInit bool) error
|
||||
GetServices(key string) ([]response.AppService, error)
|
||||
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
|
||||
GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error)
|
||||
GetParams(id uint) (*response.AppConfig, error)
|
||||
ChangeAppPort(req request.PortUpdate) error
|
||||
GetDefaultConfigByKey(key, name string) (string, error)
|
||||
@ -262,7 +265,14 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
|
||||
case constant.Sync:
|
||||
return syncAppInstallStatus(&install)
|
||||
case constant.Upgrade:
|
||||
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
|
||||
upgradeReq := request.AppInstallUpgrade{
|
||||
InstallID: install.ID,
|
||||
DetailID: req.DetailId,
|
||||
Backup: req.Backup,
|
||||
PullImage: req.PullImage,
|
||||
DockerCompose: req.DockerCompose,
|
||||
}
|
||||
return upgradeInstall(upgradeReq)
|
||||
case constant.Reload:
|
||||
return opNginx(install.ContainerName, constant.NginxReload)
|
||||
default:
|
||||
@ -484,8 +494,8 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
|
||||
func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error) {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.AppInstallID))
|
||||
var versions []dto.AppVersion
|
||||
if err != nil {
|
||||
return versions, err
|
||||
@ -506,9 +516,28 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion,
|
||||
continue
|
||||
}
|
||||
if common.CompareVersion(detail.Version, install.Version) {
|
||||
var newCompose string
|
||||
if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() {
|
||||
filename := filepath.Base(detail.DownloadUrl)
|
||||
dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml")
|
||||
statusCode, composeRes, err := httpUtil.HandleGet(dockerComposeUrl, http.MethodGet)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
if statusCode > 200 {
|
||||
return versions, err
|
||||
}
|
||||
detail.DockerCompose = string(composeRes)
|
||||
_ = appDetailRepo.Update(context.Background(), detail)
|
||||
}
|
||||
newCompose, err = getUpgradeCompose(install, detail)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
versions = append(versions, dto.AppVersion{
|
||||
Version: detail.Version,
|
||||
DetailId: detail.ID,
|
||||
Version: detail.Version,
|
||||
DetailId: detail.ID,
|
||||
DockerCompose: newCompose,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -439,12 +439,65 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
||||
func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
|
||||
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
||||
if detail.DockerCompose == "" {
|
||||
return "", nil
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
|
||||
return "", err
|
||||
}
|
||||
value, ok := composeMap["services"]
|
||||
if !ok {
|
||||
return "", buserr.New(constant.ErrFileParse)
|
||||
}
|
||||
servicesMap := value.(map[string]interface{})
|
||||
if len(servicesMap) == 1 {
|
||||
index := 0
|
||||
oldServiceName := ""
|
||||
for k := range servicesMap {
|
||||
oldServiceName = k
|
||||
index++
|
||||
if index > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
|
||||
if install.ServiceName != oldServiceName {
|
||||
delete(servicesMap, oldServiceName)
|
||||
}
|
||||
}
|
||||
envs := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
config := getAppCommonConfig(envs)
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
envs[constant.ContainerName] = install.ContainerName
|
||||
}
|
||||
config.Advanced = true
|
||||
if err := addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
paramByte, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
install.Env = string(paramByte)
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(composeByte), nil
|
||||
}
|
||||
|
||||
func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailID))
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -459,7 +512,7 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
|
||||
backupFile string
|
||||
)
|
||||
global.LOG.Infof(i18n.GetMsgWithName("UpgradeAppStart", install.Name, nil))
|
||||
if backup {
|
||||
if req.Backup {
|
||||
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
|
||||
if err == nil {
|
||||
localDir, err := loadLocalDir()
|
||||
@ -476,13 +529,13 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
|
||||
defer func() {
|
||||
if upErr != nil {
|
||||
global.LOG.Infof(i18n.GetMsgWithName("ErrAppUpgrade", install.Name, upErr))
|
||||
if backup {
|
||||
if req.Backup {
|
||||
global.LOG.Infof(i18n.GetMsgWithName("AppRecover", install.Name, nil))
|
||||
if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", Source: constant.ResourceLocal, File: backupFile}); err != nil {
|
||||
global.LOG.Errorf("recover app [%s] [%s] failed %v", install.App.Key, install.Name, err)
|
||||
}
|
||||
}
|
||||
existInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
|
||||
existInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
|
||||
if existInstall.ID > 0 {
|
||||
existInstall.Status = constant.UpgradeErr
|
||||
existInstall.Message = upErr.Error()
|
||||
@ -529,59 +582,19 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
|
||||
_, _ = scriptCmd.CombinedOutput()
|
||||
}
|
||||
|
||||
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{})
|
||||
if len(servicesMap) == 1 {
|
||||
index := 0
|
||||
oldServiceName := ""
|
||||
for k := range servicesMap {
|
||||
oldServiceName = k
|
||||
index++
|
||||
if index > 0 {
|
||||
break
|
||||
}
|
||||
var newCompose string
|
||||
if req.DockerCompose == "" {
|
||||
newCompose, upErr = getUpgradeCompose(install, detail)
|
||||
if upErr != nil {
|
||||
return
|
||||
}
|
||||
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
|
||||
if install.ServiceName != oldServiceName {
|
||||
delete(servicesMap, oldServiceName)
|
||||
}
|
||||
}
|
||||
envs := make(map[string]interface{})
|
||||
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
|
||||
return
|
||||
}
|
||||
config := getAppCommonConfig(envs)
|
||||
if config.ContainerName == "" {
|
||||
config.ContainerName = install.ContainerName
|
||||
envs[constant.ContainerName] = install.ContainerName
|
||||
}
|
||||
config.Advanced = true
|
||||
if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil {
|
||||
return
|
||||
}
|
||||
paramByte, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
install.Env = string(paramByte)
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
} else {
|
||||
newCompose = req.DockerCompose
|
||||
}
|
||||
|
||||
install.DockerCompose = string(composeByte)
|
||||
install.DockerCompose = newCompose
|
||||
install.Version = detail.Version
|
||||
install.AppDetailId = detailID
|
||||
install.AppDetailId = req.DetailID
|
||||
|
||||
content, err := fileOp.GetContent(install.GetEnvPath())
|
||||
if err != nil {
|
||||
@ -589,7 +602,7 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
|
||||
return
|
||||
}
|
||||
|
||||
if pullImage {
|
||||
if req.PullImage {
|
||||
projectName := strings.ToLower(install.Name)
|
||||
images, err := composeV2.GetDockerComposeImages(projectName, content, []byte(detail.DockerCompose))
|
||||
if err != nil {
|
||||
@ -621,6 +634,10 @@ func upgradeInstall(installID uint, detailID uint, backup, pullImage bool) error
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
envs := make(map[string]interface{})
|
||||
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
|
||||
return
|
||||
}
|
||||
envParams := make(map[string]string, len(envs))
|
||||
handleMap(envs, envParams)
|
||||
if upErr = env.Write(envParams, install.GetEnvPath()); upErr != nil {
|
||||
@ -1134,6 +1151,7 @@ func synAppInstall(containers map[string]types.Container, appInstall *model.AppI
|
||||
if len(containers) == 0 {
|
||||
appInstall.Status = constant.Error
|
||||
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
return
|
||||
}
|
||||
notFoundNames := make([]string, 0)
|
||||
@ -1178,6 +1196,7 @@ func synAppInstall(containers map[string]types.Container, appInstall *model.AppI
|
||||
appInstall.Message = msg
|
||||
appInstall.Status = constant.UnHealthy
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
|
||||
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
|
||||
@ -1223,6 +1242,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
AppName: installed.App.Name,
|
||||
AppKey: installed.App.Key,
|
||||
AppType: installed.App.Type,
|
||||
Path: installed.GetPath(),
|
||||
}
|
||||
if updated {
|
||||
installDTO.DockerCompose = installed.DockerCompose
|
||||
}
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId))
|
||||
if err != nil {
|
||||
|
@ -23,7 +23,6 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
appRouter.GET("/details/:id", baseApi.GetAppDetailByID)
|
||||
appRouter.POST("/install", baseApi.InstallApp)
|
||||
appRouter.GET("/tags", baseApi.GetAppTags)
|
||||
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
|
||||
appRouter.POST("/installed/check", baseApi.CheckAppInstalled)
|
||||
appRouter.POST("/installed/loadport", baseApi.LoadPort)
|
||||
appRouter.POST("/installed/conninfo", baseApi.LoadConnInfo)
|
||||
@ -39,5 +38,6 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
appRouter.POST("/installed/params/update", baseApi.UpdateInstalled)
|
||||
appRouter.POST("/installed/ignore", baseApi.IgnoreUpgrade)
|
||||
appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp)
|
||||
appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions)
|
||||
}
|
||||
}
|
||||
|
@ -236,4 +236,9 @@ export namespace App {
|
||||
version: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface AppUpdateVersionReq {
|
||||
appInstallID: number;
|
||||
updateVersion?: string;
|
||||
}
|
||||
}
|
||||
|
@ -79,8 +79,8 @@ export const GetAppService = (key: string | undefined) => {
|
||||
return http.get<App.AppService[]>(`apps/services/${key}`);
|
||||
};
|
||||
|
||||
export const GetAppUpdateVersions = (id: number) => {
|
||||
return http.get<any>(`apps/installed/${id}/versions`);
|
||||
export const GetAppUpdateVersions = (req: App.AppUpdateVersionReq) => {
|
||||
return http.post<any>(`apps/installed/update/versions`, req);
|
||||
};
|
||||
|
||||
export const GetAppDefaultConfig = (key: string, name: string) => {
|
||||
|
BIN
frontend/src/assets/images/theworld.png
Normal file
BIN
frontend/src/assets/images/theworld.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 333 B |
@ -1744,7 +1744,16 @@ const message = {
|
||||
showLocal: 'Show Local Application',
|
||||
reload: 'Reload',
|
||||
upgradeWarn:
|
||||
'Upgrading the application will replace the docker-compose.yml file. If there is any change, please replace it after upgrading',
|
||||
'Upgrading the application will replace the docker-compose.yml file. If there are any changes, you can click to view the file comparison',
|
||||
newVersion: 'New version',
|
||||
oldVersion: 'Current version',
|
||||
composeDiff: 'File comparison',
|
||||
showDiff: 'View comparison',
|
||||
useNew: 'Use custom version',
|
||||
useDefault: 'Use default version',
|
||||
useCustom: 'Customize docker-compose.yml',
|
||||
useCustomHelper:
|
||||
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
|
||||
},
|
||||
website: {
|
||||
website: 'Website',
|
||||
|
@ -1624,7 +1624,15 @@ const message = {
|
||||
hostModeHelper: '目前應用網路模式為 host 模式,如需放開端口,請在防火牆頁面手動放開',
|
||||
showLocal: '顯示本機應用程式',
|
||||
reload: '重載',
|
||||
upgradeWarn: '升級應用程式會取代 docker-compose.yml 文件,如有更改,請升級之後替換',
|
||||
upgradeWarn: '升級應用程式會取代 docker-compose.yml 文件,如有更改,可以點擊查看文件對比',
|
||||
newVersion: '新版本',
|
||||
oldVersion: '目前版本',
|
||||
composeDiff: '文件對比',
|
||||
showDiff: '看對比',
|
||||
useNew: '使用自訂版本',
|
||||
useDefault: '使用預設版本',
|
||||
useCustom: '自訂 docker-compose.yml',
|
||||
useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選',
|
||||
},
|
||||
website: {
|
||||
website: '網站',
|
||||
|
@ -1624,7 +1624,15 @@ const message = {
|
||||
hostModeHelper: '当前应用网络模式为 host 模式,如需放开端口,请在防火墙页面手动放开',
|
||||
showLocal: '显示本地应用',
|
||||
reload: '重载',
|
||||
upgradeWarn: '升级应用会替换 docker-compose.yml 文件,如有更改,请升级之后替换',
|
||||
upgradeWarn: '升级应用会替换 docker-compose.yml 文件,如有更改,可以点击查看文件对比',
|
||||
newVersion: '新版本',
|
||||
oldVersion: '当前版本',
|
||||
composeDiff: '文件对比',
|
||||
showDiff: '查看对比',
|
||||
useNew: '使用自定义版本',
|
||||
useDefault: '使用默认版本',
|
||||
useCustom: '自定义 docker-compose.yml',
|
||||
useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选',
|
||||
},
|
||||
website: {
|
||||
website: '网站',
|
||||
|
@ -21,10 +21,15 @@ import Components from '@/components';
|
||||
import ElementPlus from 'element-plus';
|
||||
import Fit2CloudPlus from 'fit2cloud-ui-plus';
|
||||
import * as Icons from '@element-plus/icons-vue';
|
||||
import VueDiff from 'vue-diff';
|
||||
import 'vue-diff/dist/index.css';
|
||||
import yaml from 'highlight.js/lib/languages/yaml';
|
||||
VueDiff.hljs.registerLanguage('yaml', yaml);
|
||||
|
||||
const app = createApp(App);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.use(ElementPlus);
|
||||
|
||||
app.use(VueDiff);
|
||||
app.use(Fit2CloudPlus, { locale: i18n.global.messages.value[localStorage.getItem('lang') || 'zh'] });
|
||||
|
||||
Object.keys(Icons).forEach((key) => {
|
||||
|
@ -435,7 +435,7 @@ const openOperate = (row: any, op: string) => {
|
||||
operateReq.installId = row.id;
|
||||
operateReq.operate = op;
|
||||
if (op == 'upgrade' || op == 'ignore') {
|
||||
upgradeRef.value.acceptParams(row.id, row.name, op, row.app);
|
||||
upgradeRef.value.acceptParams(row.id, row.name, row.dockerCompose, op, row.app);
|
||||
} else if (op == 'delete') {
|
||||
AppInstalledDeleteCheck(row.id).then(async (res) => {
|
||||
const items = res.data;
|
||||
|
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('app.composeDiff')"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
width="60%"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="11" :offset="1" class="mt-2">
|
||||
<el-text type="info">{{ $t('app.oldVersion') }}</el-text>
|
||||
<codemirror
|
||||
placeholder=""
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 500px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="oldContent"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="11" class="mt-2">
|
||||
<el-text type="success">{{ $t('app.newVersion') }}</el-text>
|
||||
<el-text type="warning" class="!ml-5">编辑之后点击使用自定义版本保存</el-text>
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder=""
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 500px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="newContent"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="success" @click="confirm(newContent)">
|
||||
{{ $t('app.useNew') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="confirm('')">
|
||||
{{ $t('app.useDefault') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
const extensions = [javascript(), oneDark];
|
||||
|
||||
const open = ref(false);
|
||||
const newContent = ref('');
|
||||
const oldContent = ref('');
|
||||
const em = defineEmits(['confirm']);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const acceptParams = (oldCompose: string, newCompose: string) => {
|
||||
oldContent.value = oldCompose;
|
||||
newContent.value = newCompose;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const confirm = (content: string) => {
|
||||
em('confirm', content);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="30%">
|
||||
<el-drawer :close-on-click-modal="false" :close-on-press-escape="false" v-model="open" size="50%">
|
||||
<template #header>
|
||||
<Header
|
||||
:header="$t('commons.button.' + operateReq.operate)"
|
||||
@ -7,39 +7,9 @@
|
||||
:back="handleClose"
|
||||
></Header>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form
|
||||
@submit.prevent
|
||||
ref="updateRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="operateReq"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('app.versionSelect')" prop="detailId">
|
||||
<el-select v-model="operateReq.detailId">
|
||||
<el-option
|
||||
v-for="(version, index) in versions"
|
||||
:key="index"
|
||||
:value="version.detailId"
|
||||
:label="version.version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="backup" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-checkbox v-model="operateReq.backup" :label="$t('app.backupApp')" />
|
||||
<span class="input-help">{{ $t('app.backupAppHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-text type="warning">{{ $t('app.upgradeWarn') }}</el-text>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="22" :offset="1">
|
||||
<div class="descriptions">
|
||||
<div>
|
||||
<el-descriptions direction="vertical">
|
||||
<el-descriptions-item>
|
||||
<el-link @click="toLink(app.website)">
|
||||
@ -62,6 +32,60 @@
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form
|
||||
@submit.prevent
|
||||
ref="updateRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="operateReq"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('app.versionSelect')" prop="detailId">
|
||||
<el-select v-model="operateReq.version" @change="getVersions(operateReq.version)">
|
||||
<el-option
|
||||
v-for="(version, index) in versions"
|
||||
:key="index"
|
||||
:value="version.version"
|
||||
:label="version.version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="backup" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-checkbox v-model="operateReq.backup" :label="$t('app.backupApp')" />
|
||||
<span class="input-help">{{ $t('app.backupAppHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="22" :offset="1" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-text type="warning">{{ $t('app.upgradeWarn') }}</el-text>
|
||||
<el-button class="ml-1.5" type="text" @click="openDiff()">{{ $t('app.showDiff') }}</el-button>
|
||||
<div>
|
||||
<el-checkbox v-model="useNewCompose" :label="$t('app.useCustom')" size="large" />
|
||||
</div>
|
||||
<div v-if="useNewCompose">
|
||||
<el-text type="danger">{{ $t('app.useCustomHelper') }}</el-text>
|
||||
</div>
|
||||
<codemirror
|
||||
v-if="useNewCompose"
|
||||
:autofocus="true"
|
||||
placeholder=""
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 500px); margin-top: 10px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="newCompose"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -71,6 +95,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<Diff ref="composeDiffRef" @confirm="getNewCompose" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@ -82,8 +107,14 @@ import { reactive, ref, onBeforeUnmount } from 'vue';
|
||||
import Header from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import Diff from './diff/index.vue';
|
||||
import bus from '../../bus';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
const extensions = [javascript(), oneDark];
|
||||
|
||||
const composeDiffRef = ref();
|
||||
const updateRef = ref<FormInstance>();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
@ -94,39 +125,94 @@ const operateReq = reactive({
|
||||
installId: 0,
|
||||
backup: true,
|
||||
pullImage: true,
|
||||
version: '',
|
||||
dockerCompose: '',
|
||||
});
|
||||
const resourceName = ref('');
|
||||
const rules = ref<any>({
|
||||
detailId: [Rules.requiredSelect],
|
||||
});
|
||||
const app = ref();
|
||||
const oldContent = ref('');
|
||||
const newContent = ref('');
|
||||
const em = defineEmits(['close']);
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', open);
|
||||
};
|
||||
|
||||
const newCompose = ref('');
|
||||
const useNewCompose = ref(false);
|
||||
const appInstallID = ref(0);
|
||||
|
||||
const toLink = (link: string) => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
const acceptParams = (id: number, name: string, op: string, appDetail: App.AppDetail) => {
|
||||
const openDiff = () => {
|
||||
composeDiffRef.value.acceptParams(oldContent.value, newContent.value);
|
||||
};
|
||||
|
||||
const getNewCompose = (compose: string) => {
|
||||
if (compose !== '') {
|
||||
newCompose.value = compose;
|
||||
useNewCompose.value = true;
|
||||
} else {
|
||||
newCompose.value = newContent.value;
|
||||
useNewCompose.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const initData = () => {
|
||||
newCompose.value = '';
|
||||
useNewCompose.value = false;
|
||||
operateReq.backup = true;
|
||||
operateReq.pullImage = true;
|
||||
operateReq.dockerCompose = '';
|
||||
};
|
||||
|
||||
const acceptParams = (id: number, name: string, dockerCompose: string, op: string, appDetail: App.AppDetail) => {
|
||||
initData();
|
||||
operateReq.installId = id;
|
||||
operateReq.operate = op;
|
||||
resourceName.value = name;
|
||||
app.value = appDetail;
|
||||
GetAppUpdateVersions(id).then((res) => {
|
||||
versions.value = res.data;
|
||||
oldContent.value = dockerCompose;
|
||||
appInstallID.value = id;
|
||||
getVersions('');
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const getVersions = async (version: string) => {
|
||||
const req = {
|
||||
appInstallID: appInstallID.value,
|
||||
};
|
||||
if (version !== '') {
|
||||
req['updateVersion'] = version;
|
||||
}
|
||||
try {
|
||||
const res = await GetAppUpdateVersions(req);
|
||||
versions.value = res.data || [];
|
||||
if (res.data != null && res.data.length > 0) {
|
||||
operateReq.detailId = res.data[0].detailId;
|
||||
let item = res.data[0];
|
||||
if (version != '') {
|
||||
item = res.data.find((v) => v.version === version);
|
||||
}
|
||||
operateReq.detailId = item.detailId;
|
||||
operateReq.version = item.version;
|
||||
newContent.value = item.dockerCompose;
|
||||
newCompose.value = item.dockerCompose;
|
||||
useNewCompose.value = false;
|
||||
}
|
||||
open.value = true;
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const operate = async () => {
|
||||
loading.value = true;
|
||||
if (operateReq.operate === 'upgrade') {
|
||||
if (useNewCompose.value) {
|
||||
operateReq.dockerCompose = newCompose.value;
|
||||
}
|
||||
await InstalledOp(operateReq)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('app.upgradeStart'));
|
||||
|
Loading…
Reference in New Issue
Block a user