1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 00:09:16 +08:00

feat: 增加应用备份功能

This commit is contained in:
zhengkunwang223 2022-10-12 18:57:22 +08:00 committed by zhengkunwang223
parent 7f4e8a25ea
commit 9f55523972
24 changed files with 391 additions and 49 deletions

View File

@ -5,7 +5,6 @@ system:
data_dir: /opt/1Panel/data
resource_dir: /opt/1Panel/data/resource
app_dir: /opt/1Panel/data/apps
app_oss:
mysql:
path: localhost

View File

@ -75,6 +75,21 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) DeleteAppBackup(c *gin.Context) {
var req dto.AppBackupDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := appService.DeleteBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) SearchInstalled(c *gin.Context) {
var req dto.AppInstalledRequest
if err := c.ShouldBindJSON(&req); err != nil {
@ -93,6 +108,24 @@ func (b *BaseApi) SearchInstalled(c *gin.Context) {
})
}
func (b *BaseApi) SearchInstalledBackup(c *gin.Context) {
var req dto.AppBackupRequest
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := appService.PageInstallBackups(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
func (b *BaseApi) OperateInstalled(c *gin.Context) {
var req dto.AppInstallOperate
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -92,6 +92,15 @@ type AppInstalledRequest struct {
PageInfo
}
type AppBackupRequest struct {
PageInfo
AppInstallID uint `json:"appInstallID"`
}
type AppBackupDeleteRequest struct {
Ids []uint `json:"ids"`
}
type AppOperate string
var (
@ -100,6 +109,7 @@ var (
Restart AppOperate = "restart"
Delete AppOperate = "delete"
Sync AppOperate = "sync"
Backup AppOperate = "backup"
)
type AppInstallOperate struct {

View File

@ -1,34 +1,35 @@
package model
import (
"github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/constant"
"path"
)
type AppInstall struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
AppDetailId uint `json:"appDetailId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Param string `json:"param" gorm:"type:longtext;"`
Env string `json:"env" gorm:"type:longtext;"`
DockerCompose string `json:"dockerCompose" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(256);not null"`
Description string `json:"description" gorm:"type:varchar(256);"`
Message string `json:"message" gorm:"type:longtext;"`
CanUpdate bool `json:"canUpdate"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
ServiceName string `json:"ServiceName" gorm:"type:varchar(256);not null"`
HttpPort int `json:"httpPort" gorm:"type:integer;not null"`
HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"`
App App `json:"-"`
Name string `json:"name" gorm:"type:varchar(64);not null"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
AppDetailId uint `json:"appDetailId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Param string `json:"param" gorm:"type:longtext;"`
Env string `json:"env" gorm:"type:longtext;"`
DockerCompose string `json:"dockerCompose" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(256);not null"`
Description string `json:"description" gorm:"type:varchar(256);"`
Message string `json:"message" gorm:"type:longtext;"`
CanUpdate bool `json:"canUpdate"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
ServiceName string `json:"serviceName" gorm:"type:varchar(256);not null"`
HttpPort int `json:"httpPort" gorm:"type:integer;not null"`
HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"`
App App `json:"app"`
Backups []AppInstallBackup `json:"backups"`
}
func (i *AppInstall) GetPath() string {
return path.Join(global.CONF.System.AppDir, i.App.Key, i.Name)
return path.Join(constant.AppInstallDir, i.App.Key, i.Name)
}
func (i *AppInstall) GetComposePath() string {
return path.Join(global.CONF.System.AppDir, i.App.Key, i.Name, "docker-compose.yml")
return path.Join(constant.AppInstallDir, i.App.Key, i.Name, "docker-compose.yml")
}

View File

@ -0,0 +1,9 @@
package model
type AppInstallBackup struct {
BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"`
Path string `gorm:"type:varchar(64);not null" json:"path"`
AppDetailId uint `gorm:"type:varchar(64);not null" json:"app_detail_id"`
AppInstallId uint `gorm:"type:integer;not null" json:"app_install_id"`
}

View File

@ -40,14 +40,14 @@ func (a AppInstallRepo) WithServiceName(serviceName string) DBOption {
func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
var install []model.AppInstall
db := getDb(opts...).Model(&model.AppInstall{})
err := db.Preload("App").Find(&install).Error
err := db.Preload("App").Preload("Backups").Find(&install).Error
return install, err
}
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
var install model.AppInstall
db := getDb(opts...).Model(&model.AppInstall{})
err := db.Preload("App").First(&install).Error
err := db.Preload("App").Preload("Backups").First(&install).Error
return install, err
}
@ -74,7 +74,7 @@ func (a AppInstallRepo) Page(page, size int, opts ...DBOption) (int64, []model.A
db := getDb(opts...).Model(&model.AppInstall{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Find(&apps).Error
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("App").Preload("Backups").Find(&apps).Error
return count, apps, err
}

View File

@ -0,0 +1,41 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/app/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type AppInstallBackupRepo struct {
}
func (a AppInstallBackupRepo) WithAppInstallID(appInstallID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallID)
}
}
func (a AppInstallBackupRepo) Create(ctx context.Context, backup model.AppInstallBackup) error {
return getTx(ctx).Omit(clause.Associations).Create(&backup).Error
}
func (a AppInstallBackupRepo) Delete(opts ...DBOption) error {
return getDb(opts...).Omit(clause.Associations).Delete(&model.AppInstallBackup{}).Error
}
func (a AppInstallBackupRepo) GetBy(opts ...DBOption) ([]model.AppInstallBackup, error) {
var backups []model.AppInstallBackup
if err := getDb(opts...).Find(&backups); err != nil {
return backups, nil
}
return backups, nil
}
func (a AppInstallBackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.AppInstallBackup, error) {
var backups []model.AppInstallBackup
db := getDb(opts...).Model(&model.AppInstallBackup{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Find(&backups).Error
return count, backups, err
}

View File

@ -16,6 +16,7 @@ type RepoGroup struct {
AppInstallRepo
AppInstallResourceRpo
DatabaseRepo
AppInstallBackupRepo
}
var RepoGroupApp = new(RepoGroup)

View File

@ -117,8 +117,6 @@ func (a AppService) PageInstalled(req dto.AppInstalledRequest) (int64, []dto.App
for _, in := range installed {
installDto := dto.AppInstalled{
AppInstall: in,
AppName: in.App.Name,
Icon: in.App.Icon,
}
installDTOs = append(installDTOs, installDto)
}
@ -145,6 +143,10 @@ func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO,
return appDetailDTO, nil
}
func (a AppService) PageInstallBackups(req dto.AppBackupRequest) (int64, []model.AppInstallBackup, error) {
return appInstallBackupRepo.Page(req.Page, req.PageSize, appInstallBackupRepo.WithAppInstallID(req.AppInstallID))
}
func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil {
@ -186,9 +188,7 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
}
}
tx := global.DB.Begin()
ctx := context.WithValue(context.Background(), "db", tx)
tx, ctx := getTxAndContext()
if err := appInstallRepo.Delete(ctx, install); err != nil {
tx.Rollback()
return err
@ -204,6 +204,13 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
return err
}
return nil
case dto.Backup:
tx, ctx := getTxAndContext()
if err := backupInstall(ctx, install); err != nil {
tx.Rollback()
}
tx.Commit()
return nil
default:
return errors.New("operate not support")
}
@ -284,8 +291,7 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
return err
}
tx := global.DB.Begin()
ctx := context.WithValue(context.Background(), "db", tx)
tx, ctx := getTxAndContext()
if err := appInstallRepo.Create(ctx, &appInstall); err != nil {
tx.Rollback()
return err
@ -314,6 +320,24 @@ func (a AppService) SyncAllInstalled() error {
return nil
}
func (a AppService) DeleteBackup(req dto.AppBackupDeleteRequest) error {
backups, err := appInstallBackupRepo.GetBy(commonRepo.WithIdsIn(req.Ids))
if err != nil {
return err
}
fileOp := files.NewFileOp()
for _, backup := range backups {
dst := path.Join(backup.Path, backup.Name)
if err := fileOp.DeleteFile(dst); err != nil {
continue
}
appInstallBackupRepo.Delete(commonRepo.WithIdsIn(req.Ids))
}
return nil
}
func (a AppService) GetServices(key string) ([]dto.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
@ -425,7 +449,7 @@ func (a AppService) SyncInstalled(installId uint) error {
func (a AppService) SyncAppList() error {
//TODO 从 oss 拉取最新列表
appDir := path.Join(global.CONF.System.ResourceDir, "apps")
appDir := constant.AppResourceDir
iconDir := path.Join(appDir, "icons")
listFile := path.Join(appDir, "list.json")

View File

@ -7,7 +7,6 @@ import (
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/utils/cmd"
"github.com/1Panel-dev/1Panel/utils/common"
"github.com/1Panel-dev/1Panel/utils/compose"
@ -18,6 +17,7 @@ import (
"path"
"reflect"
"strconv"
"time"
)
type DatabaseOp string
@ -52,8 +52,8 @@ func getSqlStr(key string, operate DatabaseOp, exec dto.ContainerExec) string {
switch key {
case "mysql":
if operate == Add {
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%';\"",
exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser)
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"CREATE USER '%s'@'%%' IDENTIFIED BY '%s';\" -e \"create database %s;\" -e \"GRANT ALL ON %s.* TO '%s'@'%%' IDENTIFIED BY '%s';\"",
exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser, param.Password)
}
if operate == Delete {
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"drop database %s;\" -e \"drop user %s;\" ",
@ -160,6 +160,28 @@ func deleteLink(ctx context.Context, install *model.AppInstall) error {
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func backupInstall(ctx context.Context, install model.AppInstall) error {
var backup model.AppInstallBackup
appPath := install.GetPath()
backupDir := path.Join(constant.BackupDir, install.App.Key, install.Name)
fileOp := files.NewFileOp()
now := time.Now()
day := now.Format("2006-01-02-15-04")
fileName := fmt.Sprintf("%s-%s-%s%s", install.Name, install.Version, day, ".tar.gz")
if err := fileOp.Compress([]string{appPath}, backupDir, fileName, files.TarGz); err != nil {
return err
}
backup.Name = fileName
backup.Path = backupDir
backup.AppInstallId = install.ID
backup.AppDetailId = install.AppDetailId
if err := appInstallBackupRepo.Create(ctx, backup); err != nil {
return err
}
return nil
}
func getContainerNames(install model.AppInstall) ([]string, error) {
composeMap := install.DockerCompose
envMap := make(map[string]string)
@ -220,8 +242,8 @@ func checkRequiredAndLimit(app model.App) error {
}
func copyAppData(key, version, installName string, params map[string]interface{}) (err error) {
resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", key, version)
installDir := path.Join(global.CONF.System.AppDir, key)
resourceDir := path.Join(constant.AppResourceDir, key, version)
installDir := path.Join(constant.AppInstallDir, key)
installVersionDir := path.Join(installDir, version)
fileOp := files.NewFileOp()
if err = fileOp.Copy(resourceDir, installVersionDir); err != nil {

View File

@ -33,4 +33,5 @@ var (
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
)

View File

@ -0,0 +1,13 @@
package service
import (
"context"
"github.com/1Panel-dev/1Panel/global"
"gorm.io/gorm"
)
func getTxAndContext() (tx *gorm.DB, ctx context.Context) {
tx = global.DB.Begin()
ctx = context.WithValue(context.Background(), "db", tx)
return
}

View File

@ -1,11 +1,9 @@
package configs
type System struct {
Port int `mapstructure:"port"`
DbType string `mapstructure:"db_type"`
Level string `mapstructure:"level"`
DataDir string `mapstructure:"data_dir"`
ResourceDir string `mapstructure:"resource_dir"`
AppDir string `mapstructure:"app_dir"`
AppOss string `mapstructure:"app_oss"`
Port int `mapstructure:"port"`
DbType string `mapstructure:"db_type"`
Level string `mapstructure:"level"`
DataDir string `mapstructure:"data_dir"`
AppOss string `mapstructure:"app_oss"`
}

14
backend/constant/dir.go Normal file
View File

@ -0,0 +1,14 @@
package constant
import (
"path"
)
var (
DefaultDataDir = "/opt/1Panel/data"
ResourceDir = path.Join(DefaultDataDir, "resource")
AppResourceDir = path.Join(ResourceDir, "apps")
AppInstallDir = path.Join(DefaultDataDir, "apps")
BackupDir = path.Join(DefaultDataDir, "backup")
AppBackupDir = path.Join(BackupDir, "apps")
)

View File

@ -150,6 +150,6 @@ var AddTableCronjob = &gormigrate.Migration{
var AddTableApp = &gormigrate.Migration{
ID: "20200921-add-table-app",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{})
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{}, &model.AppInstallBackup{})
},
}

View File

@ -2,9 +2,10 @@ package viper
import (
"fmt"
"github.com/1Panel-dev/1Panel/configs"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global"
"path"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
@ -30,4 +31,16 @@ func Init() {
panic(err)
}
global.CONF = serverConfig
InitDir()
}
func InitDir() {
constant.DefaultDataDir = "/opt/1Panel/data"
constant.ResourceDir = path.Join(constant.DefaultDataDir, "resource")
constant.AppResourceDir = path.Join(constant.ResourceDir, "apps")
constant.AppInstallDir = path.Join(constant.DefaultDataDir, "apps")
constant.BackupDir = path.Join(constant.DefaultDataDir, "backup")
constant.AppBackupDir = path.Join(constant.BackupDir, "apps")
//TODO 创建文件夹
}

View File

@ -23,6 +23,8 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.POST("/installed", baseApi.SearchInstalled)
appRouter.POST("/installed/op", baseApi.OperateInstalled)
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
appRouter.POST("/installed/backups", baseApi.SearchInstalledBackup)
appRouter.POST("/installed/backups/del", baseApi.DeleteAppBackup)
appRouter.GET("/services/:key", baseApi.GetServices)
}
}

View File

@ -341,6 +341,10 @@ func (f FileOp) Compress(srcRiles []string, dst string, name string, cType Compr
fileMaps[s] = base
}
if !f.Stat(dst) {
_ = f.CreateDir(dst, 0755)
}
files, err := archiver.FilesFromDisk(nil, fileMaps)
if err != nil {
return err

View File

@ -66,12 +66,12 @@ export namespace App {
name: string;
appId: string;
appDetailId: string;
params: string;
env: string;
status: string;
description: string;
message: string;
appName: string;
icon: string;
canUpdate: boolean;
app: App;
}
@ -84,4 +84,19 @@ export namespace App {
label: string;
value: string;
}
export interface AppBackupReq extends ReqPage {
appInstallId: number;
}
export interface AppBackupDelReq {
ids: number[];
}
export interface AppBackup extends CommonModel {
name: string;
path: string;
appInstallId: string;
appDetail: AppDetail;
}
}

View File

@ -37,3 +37,11 @@ export const SyncInstalledApp = () => {
export const GetAppService = (key: string | undefined) => {
return http.get<any>(`apps/services/${key}`);
};
export const GetAppBackups = (info: App.AppBackupReq) => {
return http.post<ResPage<App.AppBackup>>('apps/installed/backups', info);
};
export const DelAppBackups = (req: App.AppBackupDelReq) => {
return http.post<any>('apps/installed/backups/del', req);
};

View File

@ -418,5 +418,9 @@ export default {
deleteWarn:
'Delete the operation data and delete the operation data. This operation cannot be rolled back. Do you want to continue?',
canUpdate: 'CanUpdate',
backup: 'Backup',
backupName: 'Filename',
backupPath: 'Filepath',
backupdate: 'Backup Date',
},
};

View File

@ -410,5 +410,9 @@ export default {
deleteWarn: '删除操作会把数据一并删除,此操作不可回滚,是否继续?',
syncSuccess: '同步成功',
canUpdate: '可更新',
backup: '备份',
backupName: '文件名称',
backupPath: '文件路径',
backupdate: '备份时间',
},
};

View File

@ -0,0 +1,109 @@
<template>
<el-dialog v-model="open" :title="$t('app.backup')" width="70%" :before-close="handleClose">
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
<template #toolbar>
<el-button type="primary" plain @click="backup">{{ $t('app.backup') }}</el-button>
</template>
<el-table-column :label="$t('app.backupName')" prop="name"></el-table-column>
<el-table-column :label="$t('app.backupPath')" prop="path"></el-table-column>
<el-table-column
prop="createdAt"
:label="$t('app.backupdate')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
</el-dialog>
</template>
<script lang="ts" setup name="installBackup">
import { DelAppBackups, GetAppBackups, InstalledOp } from '@/api/modules/app';
import { reactive, ref } from 'vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
import { ElMessage } from 'element-plus';
import i18n from '@/lang';
import { useDeleteData } from '@/hooks/use-delete-data';
interface InstallRrops {
appInstallId: number;
}
const installData = ref<InstallRrops>({
appInstallId: 0,
});
let open = ref(false);
let loading = ref(false);
let data = ref<any>();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 20,
total: 0,
});
const handleClose = () => {
open.value = false;
};
const acceptParams = (props: InstallRrops) => {
installData.value.appInstallId = props.appInstallId;
search();
open.value = true;
};
const search = async () => {
const req = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
appInstallId: installData.value.appInstallId,
};
await GetAppBackups(req).then((res) => {
data.value = res.data.items;
paginationConfig.total = res.data.total;
});
};
const backup = async () => {
const req = {
installId: installData.value.appInstallId,
operate: 'backup',
};
loading.value = true;
await InstalledOp(req)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.finally(() => {
loading.value = false;
});
};
const deleteBackup = async (ids: number[]) => {
const req = {
ids: ids,
};
await useDeleteData(DelAppBackups, req, 'commons.msg.delete', loading.value);
search();
};
const buttons = [
{
label: i18n.global.t('app.delete'),
click: (row: any) => {
deleteBackup([row.id]);
},
},
];
defineExpose({
acceptParams,
});
</script>

View File

@ -10,8 +10,15 @@
</template>
</el-table-column>
<!-- <el-table-column :label="$t('app.description')" prop="description"></el-table-column> -->
<el-table-column :label="$t('app.appName')" prop="appName"></el-table-column>
<el-table-column :label="$t('app.appName')" prop="app.name"></el-table-column>
<el-table-column :label="$t('app.version')" prop="version"></el-table-column>
<el-table-column :label="$t('app.backup')">
<template #default="{ row }">
<el-link :underline="false" @click="openBackups(row.id)" type="primary">
{{ $t('app.backup') }} ({{ row.backups.length }})
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('app.status')">
<template #default="{ row }">
<el-popover
@ -35,7 +42,7 @@
show-overflow-tooltip
/>
<fu-table-operations
width="250px"
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
@ -52,6 +59,7 @@
</span>
</template>
</el-dialog>
<Backups ref="backupRef"></Backups>
</template>
<script lang="ts" setup>
@ -61,6 +69,7 @@ import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import Backups from './backups.vue';
let data = ref<any>();
let loading = ref(false);
@ -74,6 +83,7 @@ let operateReq = reactive({
installId: 0,
operate: '',
});
const backupRef = ref();
const sync = () => {
loading.value = true;
@ -178,6 +188,13 @@ const buttons = [
},
];
const openBackups = (installId: number) => {
let params = {
appInstallId: installId,
};
backupRef.value.acceptParams(params);
};
onMounted(() => {
search();
});