1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +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 data_dir: /opt/1Panel/data
resource_dir: /opt/1Panel/data/resource resource_dir: /opt/1Panel/data/resource
app_dir: /opt/1Panel/data/apps app_dir: /opt/1Panel/data/apps
app_oss:
mysql: mysql:
path: localhost path: localhost

View File

@ -75,6 +75,21 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
helper.SuccessWithData(c, nil) 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) { func (b *BaseApi) SearchInstalled(c *gin.Context) {
var req dto.AppInstalledRequest var req dto.AppInstalledRequest
if err := c.ShouldBindJSON(&req); err != nil { 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) { func (b *BaseApi) OperateInstalled(c *gin.Context) {
var req dto.AppInstallOperate var req dto.AppInstallOperate
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {

View File

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

View File

@ -1,7 +1,7 @@
package model package model
import ( import (
"github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/constant"
"path" "path"
) )
@ -19,16 +19,17 @@ type AppInstall struct {
Message string `json:"message" gorm:"type:longtext;"` Message string `json:"message" gorm:"type:longtext;"`
CanUpdate bool `json:"canUpdate"` CanUpdate bool `json:"canUpdate"`
ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"` ContainerName string `json:"containerName" gorm:"type:varchar(256);not null"`
ServiceName string `json:"ServiceName" 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"` HttpPort int `json:"httpPort" gorm:"type:integer;not null"`
HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"` HttpsPort int `json:"httpsPort" gorm:"type:integer;not null"`
App App `json:"-"` App App `json:"app"`
Backups []AppInstallBackup `json:"backups"`
} }
func (i *AppInstall) GetPath() string { 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 { 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) { func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
var install []model.AppInstall var install []model.AppInstall
db := getDb(opts...).Model(&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 return install, err
} }
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) { func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
var install model.AppInstall var install model.AppInstall
db := getDb(opts...).Model(&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 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{}) db := getDb(opts...).Model(&model.AppInstall{})
count := int64(0) count := int64(0)
db = db.Count(&count) 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 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 AppInstallRepo
AppInstallResourceRpo AppInstallResourceRpo
DatabaseRepo DatabaseRepo
AppInstallBackupRepo
} }
var RepoGroupApp = new(RepoGroup) var RepoGroupApp = new(RepoGroup)

View File

@ -117,8 +117,6 @@ func (a AppService) PageInstalled(req dto.AppInstalledRequest) (int64, []dto.App
for _, in := range installed { for _, in := range installed {
installDto := dto.AppInstalled{ installDto := dto.AppInstalled{
AppInstall: in, AppInstall: in,
AppName: in.App.Name,
Icon: in.App.Icon,
} }
installDTOs = append(installDTOs, installDto) installDTOs = append(installDTOs, installDto)
} }
@ -145,6 +143,10 @@ func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO,
return appDetailDTO, nil 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 { func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId)) install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil { if err != nil {
@ -186,9 +188,7 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
} }
} }
tx := global.DB.Begin() tx, ctx := getTxAndContext()
ctx := context.WithValue(context.Background(), "db", tx)
if err := appInstallRepo.Delete(ctx, install); err != nil { if err := appInstallRepo.Delete(ctx, install); err != nil {
tx.Rollback() tx.Rollback()
return err return err
@ -204,6 +204,13 @@ func (a AppService) OperateInstall(req dto.AppInstallOperate) error {
return err return err
} }
return nil return nil
case dto.Backup:
tx, ctx := getTxAndContext()
if err := backupInstall(ctx, install); err != nil {
tx.Rollback()
}
tx.Commit()
return nil
default: default:
return errors.New("operate not support") return errors.New("operate not support")
} }
@ -284,8 +291,7 @@ func (a AppService) Install(name string, appDetailId uint, params map[string]int
return err return err
} }
tx := global.DB.Begin() tx, ctx := getTxAndContext()
ctx := context.WithValue(context.Background(), "db", tx)
if err := appInstallRepo.Create(ctx, &appInstall); err != nil { if err := appInstallRepo.Create(ctx, &appInstall); err != nil {
tx.Rollback() tx.Rollback()
return err return err
@ -314,6 +320,24 @@ func (a AppService) SyncAllInstalled() error {
return nil 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) { func (a AppService) GetServices(key string) ([]dto.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key)) app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil { if err != nil {
@ -425,7 +449,7 @@ func (a AppService) SyncInstalled(installId uint) error {
func (a AppService) SyncAppList() error { func (a AppService) SyncAppList() error {
//TODO 从 oss 拉取最新列表 //TODO 从 oss 拉取最新列表
appDir := path.Join(global.CONF.System.ResourceDir, "apps") appDir := constant.AppResourceDir
iconDir := path.Join(appDir, "icons") iconDir := path.Join(appDir, "icons")
listFile := path.Join(appDir, "list.json") 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/dto"
"github.com/1Panel-dev/1Panel/app/model" "github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/constant" "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/cmd"
"github.com/1Panel-dev/1Panel/utils/common" "github.com/1Panel-dev/1Panel/utils/common"
"github.com/1Panel-dev/1Panel/utils/compose" "github.com/1Panel-dev/1Panel/utils/compose"
@ -18,6 +17,7 @@ import (
"path" "path"
"reflect" "reflect"
"strconv" "strconv"
"time"
) )
type DatabaseOp string type DatabaseOp string
@ -52,8 +52,8 @@ func getSqlStr(key string, operate DatabaseOp, exec dto.ContainerExec) string {
switch key { switch key {
case "mysql": case "mysql":
if operate == Add { 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'@'%%';\"", 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) exec.ContainerName, exec.Auth.RootPassword, param.DbUser, param.Password, param.DbName, param.DbName, param.DbUser, param.Password)
} }
if operate == Delete { if operate == Delete {
str = fmt.Sprintf("docker exec -i %s mysql -uroot -p%s -e \"drop database %s;\" -e \"drop user %s;\" ", 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)) 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) { func getContainerNames(install model.AppInstall) ([]string, error) {
composeMap := install.DockerCompose composeMap := install.DockerCompose
envMap := make(map[string]string) 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) { func copyAppData(key, version, installName string, params map[string]interface{}) (err error) {
resourceDir := path.Join(global.CONF.System.ResourceDir, "apps", key, version) resourceDir := path.Join(constant.AppResourceDir, key, version)
installDir := path.Join(global.CONF.System.AppDir, key) installDir := path.Join(constant.AppInstallDir, key)
installVersionDir := path.Join(installDir, version) installVersionDir := path.Join(installDir, version)
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
if err = fileOp.Copy(resourceDir, installVersionDir); err != nil { if err = fileOp.Copy(resourceDir, installVersionDir); err != nil {

View File

@ -33,4 +33,5 @@ var (
appInstallRepo = repo.RepoGroupApp.AppInstallRepo appInstallRepo = repo.RepoGroupApp.AppInstallRepo
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo 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

@ -5,7 +5,5 @@ type System struct {
DbType string `mapstructure:"db_type"` DbType string `mapstructure:"db_type"`
Level string `mapstructure:"level"` Level string `mapstructure:"level"`
DataDir string `mapstructure:"data_dir"` DataDir string `mapstructure:"data_dir"`
ResourceDir string `mapstructure:"resource_dir"`
AppDir string `mapstructure:"app_dir"`
AppOss string `mapstructure:"app_oss"` 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{ var AddTableApp = &gormigrate.Migration{
ID: "20200921-add-table-app", ID: "20200921-add-table-app",
Migrate: func(tx *gorm.DB) error { 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 ( import (
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/configs" "github.com/1Panel-dev/1Panel/configs"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/global"
"path"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -30,4 +31,16 @@ func Init() {
panic(err) panic(err)
} }
global.CONF = serverConfig 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", 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)
appRouter.POST("/installed/backups", baseApi.SearchInstalledBackup)
appRouter.POST("/installed/backups/del", baseApi.DeleteAppBackup)
appRouter.GET("/services/:key", baseApi.GetServices) 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 fileMaps[s] = base
} }
if !f.Stat(dst) {
_ = f.CreateDir(dst, 0755)
}
files, err := archiver.FilesFromDisk(nil, fileMaps) files, err := archiver.FilesFromDisk(nil, fileMaps)
if err != nil { if err != nil {
return err return err

View File

@ -66,12 +66,12 @@ export namespace App {
name: string; name: string;
appId: string; appId: string;
appDetailId: string; appDetailId: string;
params: string; env: string;
status: string; status: string;
description: string; description: string;
message: string; message: string;
appName: string;
icon: string; icon: string;
canUpdate: boolean;
app: App; app: App;
} }
@ -84,4 +84,19 @@ export namespace App {
label: string; label: string;
value: 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) => { export const GetAppService = (key: string | undefined) => {
return http.get<any>(`apps/services/${key}`); 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: deleteWarn:
'Delete the operation data and delete the operation data. This operation cannot be rolled back. Do you want to continue?', 'Delete the operation data and delete the operation data. This operation cannot be rolled back. Do you want to continue?',
canUpdate: 'CanUpdate', canUpdate: 'CanUpdate',
backup: 'Backup',
backupName: 'Filename',
backupPath: 'Filepath',
backupdate: 'Backup Date',
}, },
}; };

View File

@ -410,5 +410,9 @@ export default {
deleteWarn: '删除操作会把数据一并删除,此操作不可回滚,是否继续?', deleteWarn: '删除操作会把数据一并删除,此操作不可回滚,是否继续?',
syncSuccess: '同步成功', syncSuccess: '同步成功',
canUpdate: '可更新', 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> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column :label="$t('app.description')" prop="description"></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.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')"> <el-table-column :label="$t('app.status')">
<template #default="{ row }"> <template #default="{ row }">
<el-popover <el-popover
@ -35,7 +42,7 @@
show-overflow-tooltip show-overflow-tooltip
/> />
<fu-table-operations <fu-table-operations
width="250px" width="300px"
:ellipsis="10" :ellipsis="10"
:buttons="buttons" :buttons="buttons"
:label="$t('commons.table.operate')" :label="$t('commons.table.operate')"
@ -52,6 +59,7 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<Backups ref="backupRef"></Backups>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -61,6 +69,7 @@ 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';
let data = ref<any>(); let data = ref<any>();
let loading = ref(false); let loading = ref(false);
@ -74,6 +83,7 @@ let operateReq = reactive({
installId: 0, installId: 0,
operate: '', operate: '',
}); });
const backupRef = ref();
const sync = () => { const sync = () => {
loading.value = true; loading.value = true;
@ -178,6 +188,13 @@ const buttons = [
}, },
]; ];
const openBackups = (installId: number) => {
let params = {
appInstallId: installId,
};
backupRef.value.acceptParams(params);
};
onMounted(() => { onMounted(() => {
search(); search();
}); });