Browse Source

pref: 快照增加恢复前资源检查 (#5489)

pull/5491/head
ssongliu 5 months ago committed by GitHub
parent
commit
7e182d32a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      backend/app/dto/dashboard.go
  2. 1
      backend/app/dto/setting.go
  3. 5
      backend/app/service/dashboard.go
  4. 55
      backend/app/service/snapshot.go
  5. 8
      backend/app/service/snapshot_recover.go
  6. 151
      cmd/server/docs/docs.go
  7. 151
      cmd/server/docs/swagger.json
  8. 100
      cmd/server/docs/swagger.yaml
  9. 2
      frontend/src/api/interface/dashboard.ts
  10. 20
      frontend/src/lang/modules/en.ts
  11. 13
      frontend/src/lang/modules/tw.ts
  12. 13
      frontend/src/lang/modules/zh.ts
  13. 3
      frontend/src/views/database/redis/setting/persistence/index.vue
  14. 10
      frontend/src/views/setting/snapshot/index.vue
  15. 58
      frontend/src/views/setting/snapshot/recover/index.vue
  16. 94
      frontend/src/views/setting/snapshot/status/index.vue

2
backend/app/dto/dashboard.go

@ -30,6 +30,8 @@ type OsInfo struct {
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
DiskSize int64 `json:"diskSize"`
}
type DashboardCurrent struct {

1
backend/app/dto/setting.go

@ -143,6 +143,7 @@ type SnapshotInfo struct {
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
Version string `json:"version"`
Size int64 `json:"size"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`

5
backend/app/service/dashboard.go

@ -64,6 +64,11 @@ func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
diskInfo, err := disk.Usage(global.CONF.System.BaseDir)
if err == nil {
baseInfo.DiskSize = int64(diskInfo.Free)
}
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}

55
backend/app/service/snapshot.go

@ -48,13 +48,9 @@ func NewISnapshotService() ISnapshotService {
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
var dtoSnap []dto.SnapshotInfo
for _, systemBackup := range systemBackups {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &systemBackup); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoSnap = append(dtoSnap, item)
dtoSnap, err := loadSnapSize(systemBackups)
if err != nil {
return 0, nil, err
}
return total, dtoSnap, err
}
@ -510,3 +506,48 @@ func loadOs() string {
return hostInfo.KernelArch
}
}
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotInfo, error) {
var datas []dto.SnapshotInfo
clientMap := make(map[string]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].DefaultDownload]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].DefaultDownload))
if err != nil {
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].DefaultDownload, err)
clientMap[records[i].DefaultDownload] = loadSizeHelper{}
datas = append(datas, item)
continue
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].DefaultDownload, err)
clientMap[records[i].DefaultDownload] = loadSizeHelper{}
datas = append(datas, item)
continue
}
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
datas = append(datas, item)
clientMap[records[i].DefaultDownload] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
continue
}
if clientMap[records[i].DefaultDownload].isOk {
wg.Add(1)
go func(index int) {
item.Size, _ = clientMap[records[index].DefaultDownload].client.Size(path.Join(clientMap[records[index].DefaultDownload].backupPath, itemPath))
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
return datas, nil
}

8
backend/app/service/snapshot_recover.go

@ -73,8 +73,8 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
if snap.InterruptStep == "Readjson" {
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "AppData" {
if err := recoverAppData(snapFileDir); err != nil {
if isRecover && (req.IsNew || snap.InterruptStep == "AppData") {
if err := recoverAppData(snapFileDir); err == nil {
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
return
}
@ -163,14 +163,14 @@ func backupBeforeRecover(snap model.Snapshot, secret string) error {
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
wg.Add(5)
wg.Add(4)
itemHelper.Wg = &wg
go snapJson(itemHelper, jsonItem, baseDir)
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
go snapAppData(itemHelper, path.Join(baseDir, "docker"))
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
wg.Wait()
itemHelper.Status.AppData = constant.StatusDone
allDone, msg := checkAllDone(status)
if !allDone {

151
cmd/server/docs/docs.go

@ -1111,6 +1111,34 @@ const docTemplate = `{
}
}
},
"/containers/commit": {
"post": {
"description": "容器提交生成新镜像",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Commit Container",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ContainerCommit"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/containers/compose": {
"post": {
"security": [
@ -8006,6 +8034,48 @@ const docTemplate = `{
}
}
},
"/hosts/firewall/forward": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防火墙端口转发规则",
"consumes": [
"application/json"
],
"tags": [
"Firewall"
],
"summary": "Create group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ForwardRuleOperate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"source_port"
],
"formatEN": "update port forward rules [source_port]",
"formatZH": "更新端口转发规则 [source_port]",
"paramKeys": []
}
}
},
"/hosts/firewall/ip": {
"post": {
"security": [
@ -15136,6 +15206,32 @@ const docTemplate = `{
}
}
},
"dto.ContainerCommit": {
"type": "object",
"required": [
"containerID"
],
"properties": {
"author": {
"type": "string"
},
"comment": {
"type": "string"
},
"containerID": {
"type": "string"
},
"containerName": {
"type": "string"
},
"newImageName": {
"type": "string"
},
"pause": {
"type": "boolean"
}
}
},
"dto.ContainerListStats": {
"type": "object",
"properties": {
@ -16344,6 +16440,52 @@ const docTemplate = `{
}
}
},
"dto.ForwardRuleOperate": {
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"required": [
"operation",
"port",
"protocol",
"targetPort"
],
"properties": {
"num": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
"add",
"remove"
]
},
"port": {
"type": "string"
},
"protocol": {
"type": "string",
"enum": [
"tcp",
"udp",
"tcp/udp"
]
},
"targetIP": {
"type": "string"
},
"targetPort": {
"type": "string"
}
}
}
}
}
},
"dto.FtpBaseInfo": {
"type": "object",
"properties": {
@ -17541,6 +17683,9 @@ const docTemplate = `{
"dto.OsInfo": {
"type": "object",
"properties": {
"diskSize": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
@ -21207,6 +21352,9 @@ const docTemplate = `{
"enable": {
"type": "boolean"
},
"hsts": {
"type": "boolean"
},
"httpConfig": {
"type": "string",
"enum": [
@ -22371,6 +22519,9 @@ const docTemplate = `{
"enable": {
"type": "boolean"
},
"hsts": {
"type": "boolean"
},
"httpConfig": {
"type": "string"
}

151
cmd/server/docs/swagger.json

@ -1104,6 +1104,34 @@
}
}
},
"/containers/commit": {
"post": {
"description": "容器提交生成新镜像",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Commit Container",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ContainerCommit"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/containers/compose": {
"post": {
"security": [
@ -7999,6 +8027,48 @@
}
}
},
"/hosts/firewall/forward": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防火墙端口转发规则",
"consumes": [
"application/json"
],
"tags": [
"Firewall"
],
"summary": "Create group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ForwardRuleOperate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"source_port"
],
"formatEN": "update port forward rules [source_port]",
"formatZH": "更新端口转发规则 [source_port]",
"paramKeys": []
}
}
},
"/hosts/firewall/ip": {
"post": {
"security": [
@ -15129,6 +15199,32 @@
}
}
},
"dto.ContainerCommit": {
"type": "object",
"required": [
"containerID"
],
"properties": {
"author": {
"type": "string"
},
"comment": {
"type": "string"
},
"containerID": {
"type": "string"
},
"containerName": {
"type": "string"
},
"newImageName": {
"type": "string"
},
"pause": {
"type": "boolean"
}
}
},
"dto.ContainerListStats": {
"type": "object",
"properties": {
@ -16337,6 +16433,52 @@
}
}
},
"dto.ForwardRuleOperate": {
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"required": [
"operation",
"port",
"protocol",
"targetPort"
],
"properties": {
"num": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
"add",
"remove"
]
},
"port": {
"type": "string"
},
"protocol": {
"type": "string",
"enum": [
"tcp",
"udp",
"tcp/udp"
]
},
"targetIP": {
"type": "string"
},
"targetPort": {
"type": "string"
}
}
}
}
}
},
"dto.FtpBaseInfo": {
"type": "object",
"properties": {
@ -17534,6 +17676,9 @@
"dto.OsInfo": {
"type": "object",
"properties": {
"diskSize": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
@ -21200,6 +21345,9 @@
"enable": {
"type": "boolean"
},
"hsts": {
"type": "boolean"
},
"httpConfig": {
"type": "string",
"enum": [
@ -22364,6 +22512,9 @@
"enable": {
"type": "boolean"
},
"hsts": {
"type": "boolean"
},
"httpConfig": {
"type": "string"
}

100
cmd/server/docs/swagger.yaml

@ -403,6 +403,23 @@ definitions:
- name
- path
type: object
dto.ContainerCommit:
properties:
author:
type: string
comment:
type: string
containerID:
type: string
containerName:
type: string
newImageName:
type: string
pause:
type: boolean
required:
- containerID
type: object
dto.ContainerListStats:
properties:
containerID:
@ -1218,6 +1235,38 @@ definitions:
- type
- vars
type: object
dto.ForwardRuleOperate:
properties:
rules:
items:
properties:
num:
type: string
operation:
enum:
- add
- remove
type: string
port:
type: string
protocol:
enum:
- tcp
- udp
- tcp/udp
type: string
targetIP:
type: string
targetPort:
type: string
required:
- operation
- port
- protocol
- targetPort
type: object
type: array
type: object
dto.FtpBaseInfo:
properties:
isActive:
@ -2029,6 +2078,8 @@ definitions:
type: object
dto.OsInfo:
properties:
diskSize:
type: integer
kernelArch:
type: string
kernelVersion:
@ -4487,6 +4538,8 @@ definitions:
type: string
enable:
type: boolean
hsts:
type: boolean
httpConfig:
enum:
- HTTPSOnly
@ -5266,6 +5319,8 @@ definitions:
type: string
enable:
type: boolean
hsts:
type: boolean
httpConfig:
type: string
type: object
@ -6001,6 +6056,24 @@ paths:
formatEN: clean container [name] logs
formatZH: 清理容器 [name] 日志
paramKeys: []
/containers/commit:
post:
consumes:
- application/json
description: 容器提交生成新镜像
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ContainerCommit'
responses:
"200":
description: OK
summary: Commit Container
tags:
- Container
/containers/compose:
post:
consumes:
@ -10377,6 +10450,33 @@ paths:
summary: Create group
tags:
- Firewall
/hosts/firewall/forward:
post:
consumes:
- application/json
description: 更新防火墙端口转发规则
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ForwardRuleOperate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Create group
tags:
- Firewall
x-panel-log:
BeforeFunctions: []
bodyKeys:
- source_port
formatEN: update port forward rules [source_port]
formatZH: 更新端口转发规则 [source_port]
paramKeys: []
/hosts/firewall/ip:
post:
consumes:

2
frontend/src/api/interface/dashboard.ts

@ -5,6 +5,8 @@ export namespace Dashboard {
platformFamily: string;
kernelArch: string;
kernelVersion: string;
diskSize: number;
}
export interface BaseInfo {
websiteNumber: number;

20
frontend/src/lang/modules/en.ts

@ -1512,15 +1512,20 @@ const message = {
lastRecoverAt: 'Last recovery time',
lastRollbackAt: 'Last rollback time',
reDownload: 'Download the backup file again',
recoverRecord: 'Recover record',
statusSuccess: 'Success',
statusFailed: 'Failed',
recoverErrArch: 'Snapshot recovery between different server architectures is not supported!',
recoverErrSize: 'Detected insufficient disk space, please check or clean up and try again!',
recoverHelper:
'The recovery is about to start from snapshot {0}, and the recovery needs to restart docker and 1panel service, do you want to continue?',
recoverHelper1:
'Will start restoring from snapshot {0}, please ensure that the server architecture matches the one where the snapshot was created.',
recoverHelper2: 'Restoring snapshots between different server architectures is not supported.',
'Starting recovery from snapshot {0}, please confirm the following information before proceeding:',
recoverHelper1: 'Recovery requires restarting Docker and 1Panel services',
recoverHelper2:
'Please ensure there is sufficient disk space on the server (Snapshot file size: {0}, Available space: {1})',
recoverHelper3:
'Please ensure the server architecture matches the architecture of the server where the snapshot was created (Current server architecture: {0})',
rollback: 'Rollback',
rollbackHelper:
'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?',
'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Do you want to continue?',
upgradeHelper: 'The upgrade requires restarting the 1Panel service. Do you want to continue?',
noUpgrade: 'It is currently the latest version',
@ -1554,8 +1559,7 @@ const message = {
menu: 'Menu',
confirmMessage: 'The page will be refreshed to update the advanced menu list. Continue?',
compressPassword: 'Compression Password',
backupRecoverMessage:
'If you need to set a compression or decompression password, please enter it. (Leave blank if not needed)',
backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
},
license: {
community: 'Community Edition: ',

13
frontend/src/lang/modules/tw.ts

@ -1334,12 +1334,15 @@ const message = {
reDownload: '重新下載備份文件',
statusSuccess: '成功',
statusFailed: '失敗',
recoverHelper: '即將從快照 {0} 開始恢復恢復需要重啟 docker 以及 1panel 服務是否繼續',
recoverHelper1: '即將從快照 {0} 開始恢復請確保伺服器架構與創建快照伺服器架構信息保持一致',
recoverHelper2: '不支持在不同伺服器架構之間進行快照恢復操作',
recoverErrArch: '不支援在不同伺服器架構之間進行快照恢復操作!',
recoverErrSize: '檢測到目前磁碟空間不足請檢查或清理後重試!',
recoverHelper: '即將從快照 {0} 開始恢復恢復前請確認以下資訊',
recoverHelper1: '恢復需要重新啟動 Docker 以及 1Panel 服務',
recoverHelper2: '請確保伺服器磁碟空間充足 ( 快照檔案大小: {0}, 可用空間: {1} )',
recoverHelper3: '請確保伺服器架構與建立快照伺服器架構資訊保持一致 (目前伺服器架構: {0} )',
rollback: '回滾',
rollbackHelper:
'即將回滾本次恢復回滾將替換所有本次恢復的文件過程中可能需要重啟 docker 以及 1panel 服務是否繼續',
'即將回滾本次恢復回滾將替換所有本次恢復的檔案過程中可能需要重新啟動 Docker 以及 1Panel 服務是否繼續',
upgrading: '正在升級中請稍候...',
upgradeHelper: '升級操作需要重啟 1Panel 服務是否繼續',
@ -1444,7 +1447,7 @@ const message = {
menu: '選單',
confirmMessage: '即將刷新頁面更新高級功能菜單列表是否繼續',
compressPassword: '壓縮密碼',
backupRecoverMessage: '如果需要設定壓縮或者解壓縮密碼請輸入不填則不設定',
backupRecoverMessage: '請輸入壓縮或解壓縮密碼留空則不設定',
},
license: {
community: '社區版',

13
frontend/src/lang/modules/zh.ts

@ -1336,12 +1336,15 @@ const message = {
reDownload: '重新下载备份文件',
statusSuccess: '成功',
statusFailed: '失败',
recoverHelper: '即将从快照 {0} 开始恢复恢复需要重启 docker 以及 1panel 服务是否继续',
recoverHelper1: '即将从快照 {0} 开始恢复请确保服务器架构与创建快照服务器架构信息保持一致',
recoverHelper2: '不支持在不同服务器架构之间进行快照恢复操作',
recoverErrArch: '不支持在不同服务器架构之间进行快照恢复操作!',
recoverErrSize: '检测到当前磁盘空间不足请检查或清理后重试!',
recoverHelper: '即将从快照 {0} 开始恢复恢复前请确认以下信息',
recoverHelper1: '恢复需要重启 Docker 以及 1Panel 服务',
recoverHelper2: '请确保服务器磁盘空间充足 ( 快照文件大小: {0}, 可用空间: {1} )',
recoverHelper3: '请确保服务器架构与创建快照服务器架构信息保持一致 (当前服务器架构: {0} )',
rollback: '回滚',
rollbackHelper:
'即将回滚本次恢复回滚将替换所有本次恢复的文件过程中可能需要重启 docker 以及 1panel 服务是否继续',
'即将回滚本次恢复回滚将替换所有本次恢复的文件过程中可能需要重启 Docker 以及 1Panel 服务是否继续',
upgrading: '正在升级中请稍候...',
upgradeHelper: '升级操作需要重启 1Panel 服务是否继续',
@ -1446,7 +1449,7 @@ const message = {
menu: '菜单',
confirmMessage: '即将刷新页面更新高级功能菜单列表是否继续',
compressPassword: '压缩密码',
backupRecoverMessage: '如果需要设置压缩或者解压缩密码请输入不填则不设置',
backupRecoverMessage: '请输入压缩或解压缩密码留空则不设置',
},
license: {
community: '社区版',

3
frontend/src/views/database/redis/setting/persistence/index.vue

@ -193,7 +193,7 @@ const search = async () => {
};
const onBackup = async () => {
emit('loading', true);
await handleBackup({ name: database.value, detailName: '', type: 'redis' })
await handleBackup({ name: database.value, detailName: '', type: 'redis', secret: '' })
.then(() => {
emit('loading', false);
search();
@ -210,6 +210,7 @@ const onRecover = async () => {
name: database.value,
detailName: '',
file: currentRow.value.fileDir + '/' + currentRow.value.fileName,
secret: '',
};
emit('loading', true);
await handleRecover(param)

10
frontend/src/views/setting/snapshot/index.vue

@ -71,6 +71,14 @@
</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>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" min-width="80" prop="status">
<template #default="{ row }">
<el-button
@ -188,7 +196,7 @@
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotCreate, searchSnapshotPage, snapshotDelete, updateSnapshotDescription } from '@/api/modules/setting';
import { onMounted, reactive, ref } from 'vue';
import { dateFormat } from '@/utils/util';
import { computeSize, dateFormat } from '@/utils/util';
import { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';

58
frontend/src/views/setting/snapshot/recover/index.vue

@ -6,8 +6,36 @@
:close-on-click-modal="false"
:before-close="handleClose"
>
<el-form ref="recoverForm" label-position="left" v-loading="loading">
<el-form-item :label="$t('setting.compressPassword')" style="margin-top: 10px">
<el-form ref="recoverForm" label-position="top" v-loading="loading">
{{ $t('setting.recoverHelper', [recoverReq.name]) }}
<div style="margin-left: 20px; line-height: 32px">
<div>
<el-button style="margin-top: -4px" type="warning" link icon="WarningFilled" />
{{ $t('setting.recoverHelper1') }}
</div>
<div>
<el-button
style="margin-top: -4px"
:type="isSizeOk() ? 'success' : 'danger'"
link
:icon="isSizeOk() ? 'CircleCheckFilled' : 'CircleCloseFilled'"
/>
{{ $t('setting.recoverHelper2', [computeSize(recoverReq.size), computeSize(recoverReq.freeSize)]) }}
</div>
<div>
<el-button
style="margin-top: -4px"
:type="isArchOk() ? 'success' : 'danger'"
link
:icon="isArchOk() ? 'CircleCheckFilled' : 'CircleCloseFilled'"
/>
{{ $t('setting.recoverHelper3', [recoverReq.arch]) }}
</div>
</div>
<el-form-item v-if="!recoverReq.isNew" class="mt-2">
<el-checkbox v-model="recoverReq.reDownload">{{ $t('setting.reDownload') }}</el-checkbox>
</el-form-item>
<el-form-item :label="$t('setting.compressPassword')" class="mt-2">
<el-input v-model="recoverReq.secret" :placeholder="$t('setting.backupRecoverMessage')" />
</el-form-item>
</el-form>
@ -30,6 +58,7 @@ import { FormInstance } from 'element-plus';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { snapshotRecover } from '@/api/modules/setting';
import { computeSize } from '@/utils/util';
let loading = ref(false);
let open = ref(false);
@ -39,14 +68,22 @@ const emit = defineEmits<{ (e: 'search'): void; (e: 'close'): void }>();
interface DialogProps {
id: number;
isNew: boolean;
name: string;
reDownload: boolean;
arch: string;
size: number;
freeSize: number;
}
let recoverReq = ref({
id: 0,
isNew: true,
name: '',
reDownload: true,
secret: '',
arch: '',
size: 0,
freeSize: 0,
});
const handleClose = () => {
@ -56,12 +93,29 @@ const acceptParams = (params: DialogProps): void => {
recoverReq.value = {
id: params.id,
isNew: params.isNew,
name: params.name,
reDownload: params.reDownload,
secret: '',
arch: params.arch,
size: params.size,
freeSize: params.freeSize,
};
open.value = true;
};
const isSizeOk = () => {
if (recoverReq.value.size === 0 || recoverReq.value.freeSize === 0) {
return false;
}
return recoverReq.value.size * 2 < recoverReq.value.freeSize;
};
const isArchOk = () => {
if (recoverReq.value.arch.length === 0) {
return false;
}
return recoverReq.value.name.indexOf(recoverReq.value.arch) !== -1;
};
const submit = async () => {
loading.value = true;
await snapshotRecover({

94
frontend/src/views/setting/snapshot/status/index.vue

@ -62,7 +62,7 @@
</span>
</el-form-item>
<el-form-item>
<el-button @click="dialogVisible = true" type="primary">
<el-button @click="recoverSnapshot(false)" type="primary">
{{ $t('commons.button.retry') }}
</el-button>
</el-form-item>
@ -138,30 +138,6 @@
</el-row>
</el-form>
</el-drawer>
<el-dialog v-model="dialogVisible" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
<template #header>
<div class="card-header">
<span>{{ $t('commons.button.retry') }}</span>
</div>
</template>
<div>
<span>{{ $t('setting.reDownload') }}</span>
<el-switch style="margin-left: 15px" v-model="reDownload" />
</div>
<div style="margin-top: 15px">
<span>{{ $t('setting.recoverHelper', [snapInfo.name]) }}</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="dialogVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="doRecover(false)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</div>
<SnapRecover ref="recoverRef" @close="handleClose" />
@ -174,7 +150,7 @@ import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotRollback } from '@/api/modules/setting';
import { MsgError, MsgSuccess } from '@/utils/message';
import { MsgSuccess } from '@/utils/message';
import { loadOsInfo } from '@/api/modules/dashboard';
import SnapRecover from '@/views/setting/snapshot/recover/index.vue';
@ -182,8 +158,6 @@ const drawerVisible = ref(false);
const snapInfo = ref();
const loading = ref();
const dialogVisible = ref();
const reDownload = ref();
const recoverRef = ref();
interface DialogProps {
@ -197,61 +171,42 @@ const emit = defineEmits(['search']);
const handleClose = () => {
drawerVisible.value = false;
dialogVisible.value = false;
};
const doRecover = async (isNew: boolean) => {
let params = {
id: snapInfo.value.id,
isNew: isNew,
reDownload: reDownload.value,
secret: snapInfo.value.secret,
};
recoverRef.value.acceptParams(params);
};
const recoverSnapshot = async (isNew: boolean) => {
let msg = i18n.global.t('setting.recoverHelper', [snapInfo.value.name]);
if (
snapInfo.value.name.indexOf('amd64') === -1 &&
snapInfo.value.name.indexOf('arm64') === -1 &&
snapInfo.value.name.indexOf('armv7') === -1 &&
snapInfo.value.name.indexOf('ppc64le') === -1 &&
snapInfo.value.name.indexOf('s390x') === -1
) {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else {
const res = await loadOsInfo();
let osVal = res.data.kernelArch;
if (osVal === '') {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else if (snapInfo.value.name.indexOf(osVal) === -1) {
MsgError(i18n.global.t('setting.recoverHelper2'));
return;
}
}
loading.value = true;
await loadOsInfo()
.then((res) => {
loading.value = false;
let params = {
id: snapInfo.value.id,
isNew: isNew,
name: snapInfo.value.name,
reDownload: false,
secret: snapInfo.value.secret,
ElMessageBox.confirm(msg, i18n.global.t('commons.button.recover'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
doRecover(isNew);
});
arch: res.data.kernelArch,
size: snapInfo.value.size,
freeSize: res.data.diskSize,
};
recoverRef.value.acceptParams(params);
})
.catch(() => {
loading.value = false;
});
};
const rollbackSnapshot = async () => {
ElMessageBox.confirm(i18n.global.t('setting.rollbackHelper'), {
ElMessageBox.confirm(i18n.global.t('setting.rollbackHelper'), i18n.global.t('setting.rollback'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
await snapshotRollback({ id: snapInfo.value.id, isNew: false, reDownload: false })
await snapshotRollback({ id: snapInfo.value.id, isNew: false, reDownload: false, secret: '' })
.then(() => {
emit('search');
loading.value = false;
dialogVisible.value = false;
drawerVisible.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
@ -284,7 +239,4 @@ defineExpose({
line-height: 25px;
color: var(--el-button-text-color, var(--el-text-color-regular));
}
.card-logo {
font-size: 7px;
}
</style>

Loading…
Cancel
Save