feat: 快照列表改为异步获取大小 (#6653)

Refs #6540
This commit is contained in:
ssongliu 2024-10-08 21:58:55 +08:00 committed by GitHub
parent e3329597bc
commit e359e1c544
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 171 additions and 71 deletions

View File

@ -135,6 +135,28 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
})
}
// @Tags System Setting
// @Summary Load system snapshot size
// @Description 获取系统快照文件大小
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/size [post]
func (b *BaseApi) LoadSnapshotSize(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
accounts, err := snapshotService.LoadSize(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, accounts)
}
// @Tags System Setting
// @Summary Recover system backup
// @Description 从系统快照恢复

View File

@ -101,3 +101,11 @@ type SnapshotInfo struct {
RollbackMessage string `json:"rollbackMessage"`
LastRollbackedAt string `json:"lastRollbackedAt"`
}
type SnapshotFile struct {
ID uint `json:"id"`
Name string `json:"name"`
From string `json:"from"`
DefaultDownload string `json:"defaultDownload"`
Size int64 `json:"size"`
}

View File

@ -120,6 +120,7 @@ func (u *BackupService) CheckUsed(id uint) error {
type loadSizeHelper struct {
isOk bool
backupName string
backupPath string
client cloud_storage.CloudStorageClient
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
@ -18,7 +19,6 @@ import (
"github.com/docker/docker/api/types/image"
"github.com/google/uuid"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/v3/host"
)
@ -28,6 +28,7 @@ type SnapshotService struct {
type ISnapshotService interface {
SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, error)
LoadSnapshotData() (dto.SnapshotData, error)
SnapshotCreate(req dto.SnapshotCreate) error
SnapshotReCreate(id uint) error
@ -46,15 +47,64 @@ func NewISnapshotService() ISnapshotService {
}
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithByLikeName(req.Info))
total, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithByLikeName(req.Info))
if err != nil {
return 0, nil, err
}
dtoSnap, err := loadSnapSize(systemBackups)
if err != nil {
return 0, nil, err
var datas []dto.SnapshotInfo
for i := 0; i < len(records); i++ {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &records[i]); err != nil {
return 0, nil, err
}
datas = append(datas, item)
}
return total, dtoSnap, err
return total, datas, err
}
func (u *SnapshotService) LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, error) {
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithByLikeName(req.Info))
if err != nil {
return nil, err
}
var datas []dto.SnapshotFile
var wg sync.WaitGroup
clientMap := make(map[uint]loadSizeHelper)
for i := 0; i < len(records); i++ {
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", records[i].Name)
data := dto.SnapshotFile{ID: records[i].ID, Name: records[i].Name}
accounts := strings.Split(records[i].SourceAccountIDs, ",")
var accountNames []string
for _, account := range accounts {
itemVal, _ := strconv.Atoi(account)
if _, ok := clientMap[uint(itemVal)]; !ok {
backup, client, err := NewBackupClientWithID(uint(itemVal))
if err != nil {
global.LOG.Errorf("load backup client from db failed, err: %v", err)
clientMap[records[i].DownloadAccountID] = loadSizeHelper{}
continue
}
backupName := fmt.Sprintf("%s - %s", backup.Type, backup.Name)
clientMap[uint(itemVal)] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true, backupName: backupName}
accountNames = append(accountNames, backupName)
}
}
data.DefaultDownload = clientMap[records[i].DownloadAccountID].backupName
data.From = strings.Join(accountNames, ",")
if clientMap[records[i].DownloadAccountID].isOk {
wg.Add(1)
go func(index int) {
data.Size, _ = clientMap[records[index].DownloadAccountID].client.Size(path.Join(clientMap[records[index].DownloadAccountID].backupPath, itemPath))
datas = append(datas, data)
wg.Done()
}(i)
} else {
datas = append(datas, data)
}
}
wg.Wait()
return datas, nil
}
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
@ -175,44 +225,6 @@ func loadOs() string {
}
}
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotInfo, error) {
var datas []dto.SnapshotInfo
clientMap := make(map[uint]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &records[i]); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)
if _, ok := clientMap[records[i].DownloadAccountID]; !ok {
backup, client, err := NewBackupClientWithID(records[i].DownloadAccountID)
if err != nil {
global.LOG.Errorf("load backup client from db failed, err: %v", err)
clientMap[records[i].DownloadAccountID] = loadSizeHelper{}
datas = append(datas, item)
continue
}
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
clientMap[records[i].DownloadAccountID] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
continue
}
if clientMap[records[i].DownloadAccountID].isOk {
wg.Add(1)
go func(index int) {
item.Size, _ = clientMap[records[index].DownloadAccountID].client.Size(path.Join(clientMap[records[index].DownloadAccountID].backupPath, itemPath))
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
return datas, nil
}
func loadApps(fileOp fileUtils.FileOp) ([]dto.DataTree, error) {
var data []dto.DataTree
apps, err := appInstallRepo.ListBy()

View File

@ -19,6 +19,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
settingRouter.POST("/snapshot/recreate", baseApi.RecreateSnapshot)
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
settingRouter.POST("/snapshot/size", baseApi.LoadSnapshotSize)
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)

View File

@ -166,6 +166,13 @@ export namespace Setting {
rollbackStatus: string;
rollbackMessage: string;
}
export interface SnapshotFile {
id: number;
name: string;
from: string;
defaultDownload: string;
size: number;
}
export interface SnapshotData {
appData: Array<DataTree>;
panelData: Array<DataTree>;

View File

@ -124,6 +124,9 @@ export const snapshotRollback = (param: Setting.SnapshotRecover) => {
export const searchSnapshotPage = (param: SearchWithPage) => {
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
};
export const loadSnapshotSize = (param: SearchWithPage) => {
return http.post<Array<Setting.SnapshotFile>>(`/settings/snapshot/size`, param);
};
// upgrade
export const loadUpgradeInfo = () => {

View File

@ -38,41 +38,51 @@
<el-table-column prop="version" :label="$t('app.version')" />
<el-table-column :label="$t('setting.backupAccount')" min-width="80" prop="from">
<template #default="{ row }">
<div v-for="(item, index) of row.from.split(',')" :key="index" class="mt-1">
<div v-if="row.expand || (!row.expand && index < 3)">
<span v-if="row.from" type="info">
<span>
{{ $t('setting.' + item) }}
<div v-if="row.hasLoad">
<div v-for="(item, index) of row.from.split(',')" :key="index" class="mt-1">
<div v-if="row.expand || (!row.expand && index < 3)">
<span v-if="row.from" type="info">
<span>
{{ loadName(item) }}
</span>
<el-icon
v-if="item === row.defaultDownload"
size="12"
class="relative top-px left-1"
>
<Star />
</el-icon>
</span>
<el-icon
v-if="item === row.defaultDownload"
size="12"
class="relative top-px left-1"
>
<Star />
</el-icon>
</span>
<span v-else>-</span>
<span v-else>-</span>
</div>
</div>
<div v-if="!row.expand && row.from.split(',').length > 3">
<el-button type="primary" link @click="row.expand = true">
{{ $t('commons.button.expand') }}...
</el-button>
</div>
<div v-if="row.expand && row.from.split(',').length > 3">
<el-button type="primary" link @click="row.expand = false">
{{ $t('commons.button.collapse') }}
</el-button>
</div>
</div>
<div v-if="!row.expand && row.from.split(',').length > 3">
<el-button type="primary" link @click="row.expand = true">
{{ $t('commons.button.expand') }}...
</el-button>
</div>
<div v-if="row.expand && row.from.split(',').length > 3">
<el-button type="primary" link @click="row.expand = false">
{{ $t('commons.button.collapse') }}
</el-button>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('file.size')" prop="size" min-width="60" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.size">
{{ computeSize(row.size) }}
</span>
<span v-else>-</span>
<div v-if="row.hasLoad">
<span v-if="row.size">
{{ computeSize(row.size) }}
</span>
<span v-else>-</span>
</div>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" min-width="80" prop="status">
@ -168,6 +178,7 @@
<script setup lang="ts">
import {
searchSnapshotPage,
loadSnapshotSize,
snapshotDelete,
snapshotRecreate,
snapshotRollback,
@ -371,6 +382,7 @@ const search = async () => {
await searchSnapshotPage(params)
.then((res) => {
loading.value = false;
loadSize();
cleanData.value = false;
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
@ -380,6 +392,40 @@ const search = async () => {
});
};
const loadSize = async () => {
let params = {
info: searchName.value,
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
};
await loadSnapshotSize(params)
.then((res) => {
let stats = res.data || [];
if (stats.length === 0) {
return;
}
for (const snap of data.value) {
for (const item of stats) {
if (snap.id === item.id) {
snap.hasLoad = true;
snap.from = item.from;
snap.defaultDownload = item.defaultDownload;
snap.size = item.size;
break;
}
}
}
})
.catch(() => {
loading.value = false;
});
};
const loadName = (from: any) => {
let items = from.split(' - ');
return i18n.global.t('setting.' + items[0]) + ' ' + items[1];
};
onMounted(() => {
search();
});