feat: 增加应用更新功能

This commit is contained in:
zhengkunwang223 2022-10-13 18:56:53 +08:00 committed by zhengkunwang223
parent d7fc670136
commit 226a81ecf3
12 changed files with 167 additions and 25 deletions

View file

@ -159,3 +159,24 @@ func (b *BaseApi) GetServices(c *gin.Context) {
helper.SuccessWithData(c, services) helper.SuccessWithData(c, services)
} }
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
appInstallId := c.Param("appInstallId")
if appInstallId == "" {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, nil)
return
}
id, err := strconv.Atoi(appInstallId)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
versions, err := appService.GetUpdateVersions(uint(id))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, versions)
}

View file

@ -111,11 +111,13 @@ var (
Sync AppOperate = "sync" Sync AppOperate = "sync"
Backup AppOperate = "backup" Backup AppOperate = "backup"
Restore AppOperate = "restore" Restore AppOperate = "restore"
Update AppOperate = "update"
) )
type AppInstallOperate struct { type AppInstallOperate struct {
InstallId uint `json:"installId" validate:"required"` InstallId uint `json:"installId" validate:"required"`
BackupId uint `json:"backupId"` BackupId uint `json:"backupId"`
DetailId uint `json:"detailId"`
Operate AppOperate `json:"operate" validate:"required"` Operate AppOperate `json:"operate" validate:"required"`
} }
@ -140,3 +142,8 @@ type ContainerExec struct {
DbParam AppDatabase `json:"dbParam"` DbParam AppDatabase `json:"dbParam"`
Auth AuthParam `json:"auth"` Auth AuthParam `json:"auth"`
} }
type AppVersion struct {
Version string `json:"version"`
DetailId uint `json:"detailId"`
}

View file

@ -4,7 +4,7 @@ type AppInstallBackup struct {
BaseModel BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"` Name string `gorm:"type:varchar(64);not null" json:"name"`
Path string `gorm:"type:varchar(64);not null" json:"path"` Path string `gorm:"type:varchar(64);not null" json:"path"`
Param string `json:"param" gorm:"type:longtext;"` Param string `gorm:"type:longtext;" json:"param"`
AppDetailId uint `gorm:"type:varchar(64);not null" json:"app_detail_id"` AppDetailId uint `gorm:"type:integer;not null" json:"app_detail_id"`
AppInstallId uint `gorm:"type:integer;not null" json:"app_install_id"` AppInstallId uint `gorm:"type:integer;not null" json:"app_install_id"`
} }

View file

@ -218,6 +218,8 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
return err return err
} }
return restoreInstall(install, installBackup) return restoreInstall(install, installBackup)
case dto.Update:
return updateInstall(install.ID, req.DetailId)
default: default:
return errors.New("operate not support") return errors.New("operate not support")
} }
@ -352,25 +354,6 @@ func (a AppService) DeleteBackup(req dto.AppBackupDeleteRequest) error {
return nil return nil
} }
func (a AppService) GetServices(key string) ([]dto.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
installs, err := appInstallRepo.GetBy(appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.Running))
if err != nil {
return nil, err
}
var res []dto.AppService
for _, install := range installs {
res = append(res, dto.AppService{
Label: install.Name,
Value: install.ServiceName,
})
}
return res, nil
}
func (a AppService) SyncInstalled(installId uint) error { func (a AppService) SyncInstalled(installId uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId)) appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil { if err != nil {
@ -686,3 +669,44 @@ func syncCanUpdate() {
} }
} }
func (a AppService) GetServices(key string) ([]dto.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
installs, err := appInstallRepo.GetBy(appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.Running))
if err != nil {
return nil, err
}
var res []dto.AppService
for _, install := range installs {
res = append(res, dto.AppService{
Label: install.Name,
Value: install.ServiceName,
})
}
return res, nil
}
func (a AppService) GetUpdateVersions(installId uint) ([]dto.AppVersion, error) {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
var versions []dto.AppVersion
if err != nil {
return versions, err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(install.AppId))
if err != nil {
return versions, err
}
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
for _, detail := range details {
if common.CompareVersion(detail.Version, install.Version) {
versions = append(versions, dto.AppVersion{
Version: detail.Version,
DetailId: detail.ID,
})
}
}
return versions, nil
}

View file

@ -19,6 +19,7 @@ import (
"path" "path"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -162,6 +163,38 @@ func deleteLink(ctx context.Context, install *model.AppInstall) error {
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID)) return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
} }
func updateInstall(installId uint, detailId uint) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return err
}
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(detailId))
if err != nil {
return err
}
if install.Version == detail.Version {
return errors.New("two version is save")
}
tx, ctx := getTxAndContext()
if err := backupInstall(ctx, install); err != nil {
return err
}
tx.Commit()
if _, err = compose.Down(install.GetComposePath()); err != nil {
return err
}
install.DockerCompose = detail.DockerCompose
install.Version = detail.Version
fileOp := files.NewFileOp()
if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil {
return err
}
if _, err = compose.Up(install.GetComposePath()); err != nil {
return err
}
return appInstallRepo.Save(&install)
}
func backupInstall(ctx context.Context, install model.AppInstall) error { func backupInstall(ctx context.Context, install model.AppInstall) error {
var backup model.AppInstallBackup var backup model.AppInstallBackup
appPath := install.GetPath() appPath := install.GetPath()
@ -251,6 +284,7 @@ func restoreInstall(install model.AppInstall, backup model.AppInstallBackup) err
if out, err := compose.Up(install.GetComposePath()); err != nil { if out, err := compose.Up(install.GetComposePath()); err != nil {
return handleErr(install, err, out) return handleErr(install, err, out)
} }
install.AppDetailId = backup.AppDetailId
install.Status = constant.Running install.Status = constant.Running
return appInstallRepo.Save(&install) return appInstallRepo.Save(&install)
} }

View file

@ -20,6 +20,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/:id", baseApi.GetApp) appRouter.GET("/:id", baseApi.GetApp)
appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail) appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp) appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
appRouter.POST("/installed", baseApi.SearchInstalled) appRouter.POST("/installed", baseApi.SearchInstalled)
appRouter.POST("/installed/op", baseApi.OperateInstalled) appRouter.POST("/installed/op", baseApi.OperateInstalled)
appRouter.POST("/installed/sync", baseApi.SyncInstalled) appRouter.POST("/installed/sync", baseApi.SyncInstalled)

View file

@ -13,6 +13,9 @@ import (
) )
func CompareVersion(version1 string, version2 string) bool { func CompareVersion(version1 string, version2 string) bool {
if version1 == version2 {
return false
}
version1s := strings.Split(version1, ".") version1s := strings.Split(version1, ".")
version2s := strings.Split(version2, ".") version2s := strings.Split(version2, ".")

View file

@ -79,6 +79,7 @@ export namespace App {
installId: number; installId: number;
operate: string; operate: string;
backupId?: number; backupId?: number;
detailId?: number;
} }
export interface AppService { export interface AppService {
@ -100,4 +101,9 @@ export namespace App {
appInstallId: string; appInstallId: string;
appDetail: AppDetail; appDetail: AppDetail;
} }
export interface VersionDetail {
version: string;
detailId: number;
}
} }

View file

@ -45,3 +45,7 @@ export const GetAppBackups = (info: App.AppBackupReq) => {
export const DelAppBackups = (req: App.AppBackupDelReq) => { export const DelAppBackups = (req: App.AppBackupDelReq) => {
return http.post<any>('apps/installed/backups/del', req); return http.post<any>('apps/installed/backups/del', req);
}; };
export const GetAppUpdateVersions = (id: number) => {
return http.get<any>(`apps/installed/${id}/versions`);
};

View file

@ -425,5 +425,7 @@ export default {
restore: 'Restore', restore: 'Restore',
restoreWarn: restoreWarn:
'The rollback operation will restart the application and replace the data. This operation cannot be rolled back. Do you want to continue?', 'The rollback operation will restart the application and replace the data. This operation cannot be rolled back. Do you want to continue?',
update: 'Update',
versioneSelect: 'Please Select version',
}, },
}; };

View file

@ -416,5 +416,7 @@ export default {
backupdate: '备份时间', backupdate: '备份时间',
restore: '回滚', restore: '回滚',
restoreWarn: '回滚操作会重启应用,并替换数据,此操作不可回滚,是否继续?', restoreWarn: '回滚操作会重启应用,并替换数据,此操作不可回滚,是否继续?',
update: '更新',
versioneSelect: '请选择版本',
}, },
}; };

View file

@ -51,11 +51,30 @@
/> />
</ComplexTable> </ComplexTable>
<el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%"> <el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%">
<el-alert :title="getMsg(operateReq.operate)" type="warning" :closable="false" show-icon /> <el-alert
v-if="operateReq.operate != 'update'"
:title="getMsg(operateReq.operate)"
type="warning"
:closable="false"
show-icon
/>
<div v-else style="text-align: center">
<p>{{ $t('app.versioneSelect') }}</p>
<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>
</div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button> <el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="operate">{{ $t('commons.button.confirm') }}</el-button> <el-button type="primary" @click="operate" :disabled="versions == null">
{{ $t('commons.button.confirm') }}
</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@ -63,13 +82,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { GetAppInstalled, InstalledOp, SyncInstalledApp } from '@/api/modules/app'; import { GetAppInstalled, InstalledOp, SyncInstalledApp, GetAppUpdateVersions } from '@/api/modules/app';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import ComplexTable from '@/components/complex-table/index.vue'; import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util'; import { dateFromat } from '@/utils/util';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import Backups from './backups.vue'; import Backups from './backups.vue';
import { App } from '@/api/interface/app';
let data = ref<any>(); let data = ref<any>();
let loading = ref(false); let loading = ref(false);
@ -82,7 +102,9 @@ let open = ref(false);
let operateReq = reactive({ let operateReq = reactive({
installId: 0, installId: 0,
operate: '', operate: '',
detailId: 0,
}); });
let versions = ref<App.VersionDetail[]>();
const backupRef = ref(); const backupRef = ref();
const sync = () => { const sync = () => {
@ -112,7 +134,17 @@ const search = () => {
const openOperate = (row: any, op: string) => { const openOperate = (row: any, op: string) => {
operateReq.installId = row.id; operateReq.installId = row.id;
operateReq.operate = op; operateReq.operate = op;
open.value = true; if (op == 'update') {
GetAppUpdateVersions(row.id).then((res) => {
versions.value = res.data;
if (res.data != null && res.data.length > 0) {
operateReq.detailId = res.data[0].detailId;
}
open.value = true;
});
} else {
open.value = true;
}
}; };
const operate = async () => { const operate = async () => {
@ -162,6 +194,12 @@ const buttons = [
openOperate(row, 'sync'); openOperate(row, 'sync');
}, },
}, },
{
label: i18n.global.t('app.update'),
click: (row: any) => {
openOperate(row, 'update');
},
},
{ {
label: i18n.global.t('app.restart'), label: i18n.global.t('app.restart'),
click: (row: any) => { click: (row: any) => {