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

pull/5491/head
ssongliu 5 months ago committed by GitHub
parent e32104581b
commit 7e182d32a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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

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

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

@ -48,13 +48,9 @@ func NewISnapshotService() ISnapshotService {
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) { func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info)) total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
var dtoSnap []dto.SnapshotInfo dtoSnap, err := loadSnapSize(systemBackups)
for _, systemBackup := range systemBackups { if err != nil {
var item dto.SnapshotInfo return 0, nil, err
if err := copier.Copy(&item, &systemBackup); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
dtoSnap = append(dtoSnap, item)
} }
return total, dtoSnap, err return total, dtoSnap, err
} }
@ -510,3 +506,48 @@ func loadOs() string {
return hostInfo.KernelArch 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
}

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

@ -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": { "/containers/compose": {
"post": { "post": {
"security": [ "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": { "/hosts/firewall/ip": {
"post": { "post": {
"security": [ "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": { "dto.ContainerListStats": {
"type": "object", "type": "object",
"properties": { "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": { "dto.FtpBaseInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -17541,6 +17683,9 @@ const docTemplate = `{
"dto.OsInfo": { "dto.OsInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
"diskSize": {
"type": "integer"
},
"kernelArch": { "kernelArch": {
"type": "string" "type": "string"
}, },
@ -21207,6 +21352,9 @@ const docTemplate = `{
"enable": { "enable": {
"type": "boolean" "type": "boolean"
}, },
"hsts": {
"type": "boolean"
},
"httpConfig": { "httpConfig": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -22371,6 +22519,9 @@ const docTemplate = `{
"enable": { "enable": {
"type": "boolean" "type": "boolean"
}, },
"hsts": {
"type": "boolean"
},
"httpConfig": { "httpConfig": {
"type": "string" "type": "string"
} }

@ -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": { "/containers/compose": {
"post": { "post": {
"security": [ "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": { "/hosts/firewall/ip": {
"post": { "post": {
"security": [ "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": { "dto.ContainerListStats": {
"type": "object", "type": "object",
"properties": { "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": { "dto.FtpBaseInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -17534,6 +17676,9 @@
"dto.OsInfo": { "dto.OsInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
"diskSize": {
"type": "integer"
},
"kernelArch": { "kernelArch": {
"type": "string" "type": "string"
}, },
@ -21200,6 +21345,9 @@
"enable": { "enable": {
"type": "boolean" "type": "boolean"
}, },
"hsts": {
"type": "boolean"
},
"httpConfig": { "httpConfig": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -22364,6 +22512,9 @@
"enable": { "enable": {
"type": "boolean" "type": "boolean"
}, },
"hsts": {
"type": "boolean"
},
"httpConfig": { "httpConfig": {
"type": "string" "type": "string"
} }

@ -403,6 +403,23 @@ definitions:
- name - name
- path - path
type: object 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: dto.ContainerListStats:
properties: properties:
containerID: containerID:
@ -1218,6 +1235,38 @@ definitions:
- type - type
- vars - vars
type: object 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: dto.FtpBaseInfo:
properties: properties:
isActive: isActive:
@ -2029,6 +2078,8 @@ definitions:
type: object type: object
dto.OsInfo: dto.OsInfo:
properties: properties:
diskSize:
type: integer
kernelArch: kernelArch:
type: string type: string
kernelVersion: kernelVersion:
@ -4487,6 +4538,8 @@ definitions:
type: string type: string
enable: enable:
type: boolean type: boolean
hsts:
type: boolean
httpConfig: httpConfig:
enum: enum:
- HTTPSOnly - HTTPSOnly
@ -5266,6 +5319,8 @@ definitions:
type: string type: string
enable: enable:
type: boolean type: boolean
hsts:
type: boolean
httpConfig: httpConfig:
type: string type: string
type: object type: object
@ -6001,6 +6056,24 @@ paths:
formatEN: clean container [name] logs formatEN: clean container [name] logs
formatZH: 清理容器 [name] 日志 formatZH: 清理容器 [name] 日志
paramKeys: [] 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: /containers/compose:
post: post:
consumes: consumes:
@ -10377,6 +10450,33 @@ paths:
summary: Create group summary: Create group
tags: tags:
- Firewall - 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: /hosts/firewall/ip:
post: post:
consumes: consumes:

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

@ -1512,15 +1512,20 @@ const message = {
lastRecoverAt: 'Last recovery time', lastRecoverAt: 'Last recovery time',
lastRollbackAt: 'Last rollback time', lastRollbackAt: 'Last rollback time',
reDownload: 'Download the backup file again', 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: 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?', 'Starting recovery from snapshot {0}, please confirm the following information before proceeding:',
recoverHelper1: recoverHelper1: 'Recovery requires restarting Docker and 1Panel services',
'Will start restoring from snapshot {0}, please ensure that the server architecture matches the one where the snapshot was created.', recoverHelper2:
recoverHelper2: 'Restoring snapshots between different server architectures is not supported.', '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', rollback: 'Rollback',
rollbackHelper: 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?', upgradeHelper: 'The upgrade requires restarting the 1Panel service. Do you want to continue?',
noUpgrade: 'It is currently the latest version', noUpgrade: 'It is currently the latest version',
@ -1554,8 +1559,7 @@ const message = {
menu: 'Menu', menu: 'Menu',
confirmMessage: 'The page will be refreshed to update the advanced menu list. Continue?', confirmMessage: 'The page will be refreshed to update the advanced menu list. Continue?',
compressPassword: 'Compression Password', compressPassword: 'Compression Password',
backupRecoverMessage: backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
'If you need to set a compression or decompression password, please enter it. (Leave blank if not needed)',
}, },
license: { license: {
community: 'Community Edition: ', community: 'Community Edition: ',

@ -1334,12 +1334,15 @@ const message = {
reDownload: '', reDownload: '',
statusSuccess: '', statusSuccess: '',
statusFailed: '', statusFailed: '',
recoverHelper: ' {0} docker 1panel ', recoverErrArch: '!',
recoverHelper1: ' {0} ', recoverErrSize: '!',
recoverHelper2: '', recoverHelper: ' {0} ',
recoverHelper1: ' Docker 1Panel ',
recoverHelper2: ' ( : {0}, : {1} )',
recoverHelper3: ' (: {0} )',
rollback: '', rollback: '',
rollbackHelper: rollbackHelper:
' docker 1panel ', ' Docker 1Panel ',
upgrading: '...', upgrading: '...',
upgradeHelper: ' 1Panel ', upgradeHelper: ' 1Panel ',
@ -1444,7 +1447,7 @@ const message = {
menu: '', menu: '',
confirmMessage: '', confirmMessage: '',
compressPassword: '', compressPassword: '',
backupRecoverMessage: '', backupRecoverMessage: '',
}, },
license: { license: {
community: '', community: '',

@ -1336,12 +1336,15 @@ const message = {
reDownload: '', reDownload: '',
statusSuccess: '', statusSuccess: '',
statusFailed: '', statusFailed: '',
recoverHelper: ' {0} docker 1panel ', recoverErrArch: '!',
recoverHelper1: ' {0} ', recoverErrSize: '!',
recoverHelper2: '', recoverHelper: ' {0} ',
recoverHelper1: ' Docker 1Panel ',
recoverHelper2: ' ( : {0}, : {1} )',
recoverHelper3: ' (: {0} )',
rollback: '', rollback: '',
rollbackHelper: rollbackHelper:
' docker 1panel ', ' Docker 1Panel ',
upgrading: '...', upgrading: '...',
upgradeHelper: ' 1Panel ', upgradeHelper: ' 1Panel ',
@ -1446,7 +1449,7 @@ const message = {
menu: '', menu: '',
confirmMessage: '', confirmMessage: '',
compressPassword: '', compressPassword: '',
backupRecoverMessage: '', backupRecoverMessage: '',
}, },
license: { license: {
community: '', community: '',

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

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

@ -6,8 +6,36 @@
:close-on-click-modal="false" :close-on-click-modal="false"
:before-close="handleClose" :before-close="handleClose"
> >
<el-form ref="recoverForm" label-position="left" v-loading="loading"> <el-form ref="recoverForm" label-position="top" v-loading="loading">
<el-form-item :label="$t('setting.compressPassword')" style="margin-top: 10px"> {{ $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-input v-model="recoverReq.secret" :placeholder="$t('setting.backupRecoverMessage')" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -30,6 +58,7 @@ import { FormInstance } from 'element-plus';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { snapshotRecover } from '@/api/modules/setting'; import { snapshotRecover } from '@/api/modules/setting';
import { computeSize } from '@/utils/util';
let loading = ref(false); let loading = ref(false);
let open = ref(false); let open = ref(false);
@ -39,14 +68,22 @@ const emit = defineEmits<{ (e: 'search'): void; (e: 'close'): void }>();
interface DialogProps { interface DialogProps {
id: number; id: number;
isNew: boolean; isNew: boolean;
name: string;
reDownload: boolean; reDownload: boolean;
arch: string;
size: number;
freeSize: number;
} }
let recoverReq = ref({ let recoverReq = ref({
id: 0, id: 0,
isNew: true, isNew: true,
name: '',
reDownload: true, reDownload: true,
secret: '', secret: '',
arch: '',
size: 0,
freeSize: 0,
}); });
const handleClose = () => { const handleClose = () => {
@ -56,12 +93,29 @@ const acceptParams = (params: DialogProps): void => {
recoverReq.value = { recoverReq.value = {
id: params.id, id: params.id,
isNew: params.isNew, isNew: params.isNew,
name: params.name,
reDownload: params.reDownload, reDownload: params.reDownload,
secret: '', secret: '',
arch: params.arch,
size: params.size,
freeSize: params.freeSize,
}; };
open.value = true; 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 () => { const submit = async () => {
loading.value = true; loading.value = true;
await snapshotRecover({ await snapshotRecover({

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

Loading…
Cancel
Save