feat: 应用升级支持对比 docker-compose 文件 (#5274)

Refs https://github.com/1Panel-dev/1Panel/issues/2826
This commit is contained in:
zhengkunwang 2024-06-04 16:50:31 +08:00 committed by GitHub
parent 0182586869
commit 8b781d466a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 420 additions and 144 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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,
})
}
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -236,4 +236,9 @@ export namespace App {
version: string;
icon: string;
}
export interface AppUpdateVersionReq {
appInstallID: number;
updateVersion?: string;
}
}

View File

@ -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) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

View File

@ -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',

View File

@ -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: '網站',

View File

@ -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: '网站',

View File

@ -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) => {

View File

@ -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;

View File

@ -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>

View File

@ -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'));