mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-09-08 15:44:55 +08:00
feat: 增加应用更新功能
This commit is contained in:
parent
d7fc670136
commit
226a81ecf3
12 changed files with 167 additions and 25 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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, ".")
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`);
|
||||||
|
};
|
||||||
|
|
|
@ -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',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -416,5 +416,7 @@ export default {
|
||||||
backupdate: '备份时间',
|
backupdate: '备份时间',
|
||||||
restore: '回滚',
|
restore: '回滚',
|
||||||
restoreWarn: '回滚操作会重启应用,并替换数据,此操作不可回滚,是否继续?',
|
restoreWarn: '回滚操作会重启应用,并替换数据,此操作不可回滚,是否继续?',
|
||||||
|
update: '更新',
|
||||||
|
versioneSelect: '请选择版本',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue