Adapt console for restoring from backup root

Signed-off-by: JohnNiang <johnniang@foxmail.com>
pull/6486/head
Ryan Wang 2024-08-21 11:46:30 +08:00 committed by JohnNiang
parent 3460d4c94b
commit 6cd8dc8555
8 changed files with 189 additions and 35 deletions

View File

@ -1,11 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Backup } from "@halo-dev/api-client"; import { formatDatetime } from "@/utils/date";
import type { BackupFile } from "@halo-dev/api-client";
import { consoleApiClient } from "@halo-dev/api-client"; import { consoleApiClient } from "@halo-dev/api-client";
import { import {
Dialog, Dialog,
Toast, Toast,
VAlert, VAlert,
VButton, VButton,
VEntity,
VEntityField, VEntityField,
VLoading, VLoading,
VTabItem, VTabItem,
@ -13,23 +15,15 @@ import {
} from "@halo-dev/components"; } from "@halo-dev/components";
import { useMutation, useQuery } from "@tanstack/vue-query"; import { useMutation, useQuery } from "@tanstack/vue-query";
import axios from "axios"; import axios from "axios";
import prettyBytes from "pretty-bytes";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import BackupListItem from "../components/BackupListItem.vue";
import { useBackupFetch } from "../composables/use-backup";
const { t } = useI18n(); const { t } = useI18n();
const { data: backups } = useBackupFetch();
const normalBackups = computed(() => {
return backups.value?.items.filter((item) => {
return item.status?.phase === "SUCCEEDED";
});
});
const complete = ref(false); const complete = ref(false);
const showUploader = ref(false); const showUploader = ref(false);
const activeTabId = ref("local"); const activeTabId = ref<"local" | "remote" | "backups">("local");
const onProcessCompleted = () => { const onProcessCompleted = () => {
Dialog.success({ Dialog.success({
@ -67,14 +61,26 @@ const { isLoading: downloading, mutate: handleRemoteDownload } = useMutation({
}, },
}); });
function handleRestoreFromBackup(backup: Backup) { const { data: backupFiles } = useQuery({
queryKey: ["backup-files", activeTabId],
queryFn: async () => {
const { data } = await consoleApiClient.migration.getBackupFiles();
return data;
},
enabled: computed(() => activeTabId.value === "backups"),
});
function handleRestoreFromBackup(backupFile: BackupFile) {
Dialog.info({ Dialog.info({
title: t("core.backup.operations.restore_by_backup.title"), title: t("core.backup.operations.restore_by_backup.title"),
description: t("core.backup.operations.restore_by_backup.description", {
filename: backupFile.filename,
}),
confirmText: t("core.common.buttons.confirm"), confirmText: t("core.common.buttons.confirm"),
showCancel: false, cancelText: t("core.common.buttons.cancel"),
async onConfirm() { async onConfirm() {
await consoleApiClient.migration.restoreBackup({ await consoleApiClient.migration.restoreBackup({
backupName: backup.metadata.name, filename: backupFile.filename,
}); });
setTimeout(() => { setTimeout(() => {
onProcessCompleted(); onProcessCompleted();
@ -172,17 +178,31 @@ useQuery({
:label="$t('core.backup.restore.tabs.backup.label')" :label="$t('core.backup.restore.tabs.backup.label')"
> >
<ul <ul
class="box-border h-full w-full divide-y divide-gray-100 overflow-hidden rounded-base" class="box-border h-full w-full divide-y divide-gray-100 overflow-hidden rounded-base border"
role="list" role="list"
> >
<li v-for="(backup, index) in normalBackups" :key="index"> <li v-for="backupFile in backupFiles" :key="backupFile.filename">
<BackupListItem :show-operations="false" :backup="backup"> <VEntity>
<template #start>
<VEntityField
:title="backupFile.filename"
:description="prettyBytes(backupFile.size || 0)"
>
</VEntityField>
</template>
<template #end> <template #end>
<VEntityField v-permission="['system:themes:manage']"> <VEntityField v-if="backupFile.lastModifiedTime">
<template #description>
<span class="truncate text-xs tabular-nums text-gray-500">
{{ formatDatetime(backupFile.lastModifiedTime) }}
</span>
</template>
</VEntityField>
<VEntityField v-permission="['system:migrations:manage']">
<template #description> <template #description>
<VButton <VButton
size="sm" size="sm"
@click="handleRestoreFromBackup(backup)" @click="handleRestoreFromBackup(backupFile)"
> >
{{ {{
$t("core.backup.operations.restore_by_backup.button") $t("core.backup.operations.restore_by_backup.button")
@ -191,7 +211,7 @@ useQuery({
</template> </template>
</VEntityField> </VEntityField>
</template> </template>
</BackupListItem> </VEntity>
</li> </li>
</ul> </ul>
</VTabItem> </VTabItem>

View File

@ -93,6 +93,7 @@ models/auth-provider-list.ts
models/auth-provider-spec.ts models/auth-provider-spec.ts
models/auth-provider.ts models/auth-provider.ts
models/author.ts models/author.ts
models/backup-file.ts
models/backup-list.ts models/backup-list.ts
models/backup-spec.ts models/backup-spec.ts
models/backup-status.ts models/backup-status.ts

View File

@ -21,6 +21,8 @@ import globalAxios from 'axios';
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common';
// @ts-ignore // @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import { BackupFile } from '../models';
/** /**
* MigrationV1alpha1ConsoleApi - axios parameter creator * MigrationV1alpha1ConsoleApi - axios parameter creator
* @export * @export
@ -39,7 +41,7 @@ export const MigrationV1alpha1ConsoleApiAxiosParamCreator = function (configurat
assertParamExists('downloadBackups', 'name', name) assertParamExists('downloadBackups', 'name', name)
// verify required parameter 'filename' is not null or undefined // verify required parameter 'filename' is not null or undefined
assertParamExists('downloadBackups', 'filename', filename) assertParamExists('downloadBackups', 'filename', filename)
const localVarPath = `/apis/api.console.migration.halo.run/v1alpha1/backups/{name}/files/{filename}` const localVarPath = `/apis/console.api.migration.halo.run/v1alpha1/backups/{name}/files/{filename}`
.replace(`{${"name"}}`, encodeURIComponent(String(name))) .replace(`{${"name"}}`, encodeURIComponent(String(name)))
.replace(`{${"filename"}}`, encodeURIComponent(String(filename))); .replace(`{${"filename"}}`, encodeURIComponent(String(filename)));
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
@ -63,6 +65,43 @@ export const MigrationV1alpha1ConsoleApiAxiosParamCreator = function (configurat
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Get backup files from backup root.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getBackupFiles: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/apis/console.api.migration.halo.run/v1alpha1/backup-files`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication basicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication bearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -77,11 +116,12 @@ export const MigrationV1alpha1ConsoleApiAxiosParamCreator = function (configurat
* @param {string} [backupName] Backup metadata name. * @param {string} [backupName] Backup metadata name.
* @param {string} [downloadUrl] Remote backup HTTP URL. * @param {string} [downloadUrl] Remote backup HTTP URL.
* @param {File} [file] * @param {File} [file]
* @param {string} [filename] Filename of backup file in backups root.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
restoreBackup: async (backupName?: string, downloadUrl?: string, file?: File, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => { restoreBackup: async (backupName?: string, downloadUrl?: string, file?: File, filename?: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/apis/api.console.migration.halo.run/v1alpha1/restorations`; const localVarPath = `/apis/console.api.migration.halo.run/v1alpha1/restorations`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions; let baseOptions;
@ -115,6 +155,10 @@ export const MigrationV1alpha1ConsoleApiAxiosParamCreator = function (configurat
localVarFormParams.append('file', file as any); localVarFormParams.append('file', file as any);
} }
if (filename !== undefined) {
localVarFormParams.append('filename', filename as any);
}
localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
@ -151,16 +195,28 @@ export const MigrationV1alpha1ConsoleApiFp = function(configuration?: Configurat
const localVarOperationServerBasePath = operationServerMap['MigrationV1alpha1ConsoleApi.downloadBackups']?.[localVarOperationServerIndex]?.url; const localVarOperationServerBasePath = operationServerMap['MigrationV1alpha1ConsoleApi.downloadBackups']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
}, },
/**
* Get backup files from backup root.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getBackupFiles(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BackupFile>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getBackupFiles(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['MigrationV1alpha1ConsoleApi.getBackupFiles']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/** /**
* Restore backup by uploading file or providing download link or backup name. * Restore backup by uploading file or providing download link or backup name.
* @param {string} [backupName] Backup metadata name. * @param {string} [backupName] Backup metadata name.
* @param {string} [downloadUrl] Remote backup HTTP URL. * @param {string} [downloadUrl] Remote backup HTTP URL.
* @param {File} [file] * @param {File} [file]
* @param {string} [filename] Filename of backup file in backups root.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async restoreBackup(backupName?: string, downloadUrl?: string, file?: File, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { async restoreBackup(backupName?: string, downloadUrl?: string, file?: File, filename?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreBackup(backupName, downloadUrl, file, options); const localVarAxiosArgs = await localVarAxiosParamCreator.restoreBackup(backupName, downloadUrl, file, filename, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['MigrationV1alpha1ConsoleApi.restoreBackup']?.[localVarOperationServerIndex]?.url; const localVarOperationServerBasePath = operationServerMap['MigrationV1alpha1ConsoleApi.restoreBackup']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
@ -184,6 +240,14 @@ export const MigrationV1alpha1ConsoleApiFactory = function (configuration?: Conf
downloadBackups(requestParameters: MigrationV1alpha1ConsoleApiDownloadBackupsRequest, options?: RawAxiosRequestConfig): AxiosPromise<void> { downloadBackups(requestParameters: MigrationV1alpha1ConsoleApiDownloadBackupsRequest, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.downloadBackups(requestParameters.name, requestParameters.filename, options).then((request) => request(axios, basePath)); return localVarFp.downloadBackups(requestParameters.name, requestParameters.filename, options).then((request) => request(axios, basePath));
}, },
/**
* Get backup files from backup root.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getBackupFiles(options?: RawAxiosRequestConfig): AxiosPromise<Array<BackupFile>> {
return localVarFp.getBackupFiles(options).then((request) => request(axios, basePath));
},
/** /**
* Restore backup by uploading file or providing download link or backup name. * Restore backup by uploading file or providing download link or backup name.
* @param {MigrationV1alpha1ConsoleApiRestoreBackupRequest} requestParameters Request parameters. * @param {MigrationV1alpha1ConsoleApiRestoreBackupRequest} requestParameters Request parameters.
@ -191,7 +255,7 @@ export const MigrationV1alpha1ConsoleApiFactory = function (configuration?: Conf
* @throws {RequiredError} * @throws {RequiredError}
*/ */
restoreBackup(requestParameters: MigrationV1alpha1ConsoleApiRestoreBackupRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<void> { restoreBackup(requestParameters: MigrationV1alpha1ConsoleApiRestoreBackupRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<void> {
return localVarFp.restoreBackup(requestParameters.backupName, requestParameters.downloadUrl, requestParameters.file, options).then((request) => request(axios, basePath)); return localVarFp.restoreBackup(requestParameters.backupName, requestParameters.downloadUrl, requestParameters.file, requestParameters.filename, options).then((request) => request(axios, basePath));
}, },
}; };
}; };
@ -243,6 +307,13 @@ export interface MigrationV1alpha1ConsoleApiRestoreBackupRequest {
* @memberof MigrationV1alpha1ConsoleApiRestoreBackup * @memberof MigrationV1alpha1ConsoleApiRestoreBackup
*/ */
readonly file?: File readonly file?: File
/**
* Filename of backup file in backups root.
* @type {string}
* @memberof MigrationV1alpha1ConsoleApiRestoreBackup
*/
readonly filename?: string
} }
/** /**
@ -263,6 +334,16 @@ export class MigrationV1alpha1ConsoleApi extends BaseAPI {
return MigrationV1alpha1ConsoleApiFp(this.configuration).downloadBackups(requestParameters.name, requestParameters.filename, options).then((request) => request(this.axios, this.basePath)); return MigrationV1alpha1ConsoleApiFp(this.configuration).downloadBackups(requestParameters.name, requestParameters.filename, options).then((request) => request(this.axios, this.basePath));
} }
/**
* Get backup files from backup root.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof MigrationV1alpha1ConsoleApi
*/
public getBackupFiles(options?: RawAxiosRequestConfig) {
return MigrationV1alpha1ConsoleApiFp(this.configuration).getBackupFiles(options).then((request) => request(this.axios, this.basePath));
}
/** /**
* Restore backup by uploading file or providing download link or backup name. * Restore backup by uploading file or providing download link or backup name.
* @param {MigrationV1alpha1ConsoleApiRestoreBackupRequest} requestParameters Request parameters. * @param {MigrationV1alpha1ConsoleApiRestoreBackupRequest} requestParameters Request parameters.
@ -271,7 +352,7 @@ export class MigrationV1alpha1ConsoleApi extends BaseAPI {
* @memberof MigrationV1alpha1ConsoleApi * @memberof MigrationV1alpha1ConsoleApi
*/ */
public restoreBackup(requestParameters: MigrationV1alpha1ConsoleApiRestoreBackupRequest = {}, options?: RawAxiosRequestConfig) { public restoreBackup(requestParameters: MigrationV1alpha1ConsoleApiRestoreBackupRequest = {}, options?: RawAxiosRequestConfig) {
return MigrationV1alpha1ConsoleApiFp(this.configuration).restoreBackup(requestParameters.backupName, requestParameters.downloadUrl, requestParameters.file, options).then((request) => request(this.axios, this.basePath)); return MigrationV1alpha1ConsoleApiFp(this.configuration).restoreBackup(requestParameters.backupName, requestParameters.downloadUrl, requestParameters.file, requestParameters.filename, options).then((request) => request(this.axios, this.basePath));
} }
} }

View File

@ -0,0 +1,42 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.19.0-SNAPSHOT
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface BackupFile
*/
export interface BackupFile {
/**
*
* @type {string}
* @memberof BackupFile
*/
'filename'?: string;
/**
*
* @type {string}
* @memberof BackupFile
*/
'lastModifiedTime'?: string;
/**
*
* @type {number}
* @memberof BackupFile
*/
'size'?: number;
}

View File

@ -11,6 +11,7 @@ export * from './auth-provider-list';
export * from './auth-provider-spec'; export * from './auth-provider-spec';
export * from './author'; export * from './author';
export * from './backup'; export * from './backup';
export * from './backup-file';
export * from './backup-list'; export * from './backup-list';
export * from './backup-spec'; export * from './backup-spec';
export * from './backup-status'; export * from './backup-status';

View File

@ -1252,8 +1252,12 @@ core:
qrcode: qrcode:
label: "Use the validator application to scan the QR code below:" label: "Use the validator application to scan the QR code below:"
manual: manual:
label: "If you can't scan the QR code, click to view the alternative steps." label: >-
help: "Manually configure the validator application with the following code:" If you can't scan the QR code, click to view the alternative
steps.
help: >-
Manually configure the validator application with the
following code:
pat: pat:
operations: operations:
delete: delete:
@ -1460,7 +1464,10 @@ core:
button: Download and restore button: Download and restore
restore_by_backup: restore_by_backup:
button: Restore button: Restore
title: Restore from this backup title: Restore from backup file
description: >-
After clicking OK, data will be restored from the backup file
{filename}.
list: list:
phases: phases:
pending: Pending pending: Pending
@ -1490,7 +1497,7 @@ core:
fields: fields:
url: Remote URL url: Remote URL
backup: backup:
label: Restore from backup label: Restore from backup files
exception: exception:
not_found: not_found:
message: Page not found message: Page not found

View File

@ -1362,7 +1362,8 @@ core:
button: 下载并恢复 button: 下载并恢复
restore_by_backup: restore_by_backup:
button: 恢复 button: 恢复
title: 从此备份进行恢复 title: 从备份文件恢复
description: 点击确定后,将从备份文件 {filename} 恢复数据。
list: list:
phases: phases:
pending: 准备中 pending: 准备中
@ -1386,7 +1387,7 @@ core:
fields: fields:
url: 下载地址 url: 下载地址
backup: backup:
label: 从备份恢复 label: 从备份文件恢复
exception: exception:
not_found: not_found:
message: 没有找到该页面 message: 没有找到该页面

View File

@ -1343,7 +1343,8 @@ core:
button: 下載並還原 button: 下載並還原
restore_by_backup: restore_by_backup:
button: 還原 button: 還原
title: 確認要從此備份進行還原嗎? title: 從備份檔案恢復
description: 點選確定後,將從備份檔案 {filename} 還原資料。
list: list:
phases: phases:
pending: 準備中 pending: 準備中
@ -1367,7 +1368,7 @@ core:
fields: fields:
url: 下載地址 url: 下載地址
backup: backup:
label: 從備份還原 label: 從備份檔案恢復
exception: exception:
not_found: not_found:
message: 沒有找到該頁面 message: 沒有找到該頁面