mirror of https://github.com/halo-dev/halo
feat: add restore by backup record supports (#4511)
#### What type of PR is this? /area console /kind feature /milestone 2.9.x #### What this PR does / why we need it: 支持选择已有备份进行恢复。 <img width="1628" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/5e3169f7-a604-4e20-9c4e-f6bc68c58d59"> #### Special notes for your reviewer: 需要测试在恢复界面通过选择已有备份进行恢复的功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 系统恢复功能支持选择已有备份进行系统恢复。 ```pull/4533/head
parent
799a897622
commit
6dd77af7f8
|
@ -1003,6 +1003,9 @@ core:
|
|||
toast_success: Requested to restart
|
||||
remote_download:
|
||||
button: Download and restore
|
||||
restore_by_backup:
|
||||
button: Restore
|
||||
title: Are you sure you want to restore from this backup?
|
||||
list:
|
||||
phases:
|
||||
pending: Pending
|
||||
|
@ -1025,6 +1028,8 @@ core:
|
|||
label: Remote
|
||||
fields:
|
||||
url: Remote URL
|
||||
backup:
|
||||
label: Restore from backup
|
||||
exception:
|
||||
not_found:
|
||||
message: Page not found
|
||||
|
|
|
@ -1003,6 +1003,9 @@ core:
|
|||
toast_success: 已请求重启
|
||||
remote_download:
|
||||
button: 下载并恢复
|
||||
restore_by_backup:
|
||||
button: 恢复
|
||||
title: 确认要从此备份进行恢复吗?
|
||||
list:
|
||||
phases:
|
||||
pending: 准备中
|
||||
|
@ -1025,6 +1028,8 @@ core:
|
|||
label: 远程恢复
|
||||
fields:
|
||||
url: 下载地址
|
||||
backup:
|
||||
label: 从备份恢复
|
||||
exception:
|
||||
not_found:
|
||||
message: 没有找到该页面
|
||||
|
|
|
@ -1003,6 +1003,9 @@ core:
|
|||
toast_success: 已請求重啟
|
||||
remote_download:
|
||||
button: 下載並還原
|
||||
restore_by_backup:
|
||||
button: 還原
|
||||
title: 確認要從此備份進行還原嗎?
|
||||
list:
|
||||
phases:
|
||||
pending: 準備中
|
||||
|
@ -1016,15 +1019,17 @@ core:
|
|||
first: 1. 還原過程可能需要較長時間,期間請勿重新整理頁面。
|
||||
second: 2. 在還原過程中,雖然已有的資料不會被清除,但若有衝突的資料將被覆蓋。
|
||||
third: 3. 還原完成後需要重新啟動 Halo 才能正常載入系統資源。
|
||||
complete: 恢復完成,等待重啟...
|
||||
complete: 還原完成,等待重啟...
|
||||
start: 開始還原
|
||||
tabs:
|
||||
local:
|
||||
label: 上傳
|
||||
remote:
|
||||
label: 遠程恢復
|
||||
label: 遠程還原
|
||||
fields:
|
||||
url: 下載地址
|
||||
backup:
|
||||
label: 從備份還原
|
||||
exception:
|
||||
not_found:
|
||||
message: 沒有找到該頁面
|
||||
|
|
|
@ -23,9 +23,15 @@ import type { OperationItem } from "packages/shared/dist";
|
|||
const queryClient = useQueryClient();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
backup: Backup;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
backup: Backup;
|
||||
showOperations: boolean;
|
||||
}>(),
|
||||
{
|
||||
showOperations: true,
|
||||
}
|
||||
);
|
||||
|
||||
const { backup } = toRefs(props);
|
||||
|
||||
|
@ -185,8 +191,9 @@ const { operationItems } = useOperationItemExtensionPoint<Backup>(
|
|||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<slot name="end"></slot>
|
||||
</template>
|
||||
<template #dropdownItems>
|
||||
<template v-if="showOperations" #dropdownItems>
|
||||
<EntityDropdownItems :dropdown-items="operationItems" :item="backup" />
|
||||
</template>
|
||||
</VEntity>
|
||||
|
|
|
@ -1,9 +1,45 @@
|
|||
import { apiClient } from "@/utils/api-client";
|
||||
import { Dialog, Toast } from "@halo-dev/components";
|
||||
import { useQueryClient } from "@tanstack/vue-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
import dayjs from "dayjs";
|
||||
import { BackupStatusPhaseEnum } from "@halo-dev/api-client";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export function useBackupFetch() {
|
||||
return useQuery({
|
||||
queryKey: ["backups"],
|
||||
queryFn: async () => {
|
||||
const { data } =
|
||||
await apiClient.extension.backup.listmigrationHaloRunV1alpha1Backup({
|
||||
sort: ["metadata.creationTimestamp,desc"],
|
||||
});
|
||||
return data;
|
||||
},
|
||||
refetchInterval(data) {
|
||||
const deletingBackups = data?.items.filter((backup) => {
|
||||
return !!backup.metadata.deletionTimestamp;
|
||||
});
|
||||
|
||||
if (deletingBackups?.length) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
const pendingBackups = data?.items.filter((backup) => {
|
||||
return (
|
||||
backup.status?.phase === BackupStatusPhaseEnum.Pending ||
|
||||
backup.status?.phase === BackupStatusPhaseEnum.Running
|
||||
);
|
||||
});
|
||||
|
||||
if (pendingBackups?.length) {
|
||||
return 3000;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useBackup() {
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
|
|
|
@ -1,47 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { BackupStatusPhaseEnum } from "@halo-dev/api-client";
|
||||
import { VButton, VEmpty, VLoading } from "@halo-dev/components";
|
||||
import BackupListItem from "../components/BackupListItem.vue";
|
||||
import { useBackupFetch } from "../composables/use-backup";
|
||||
|
||||
const {
|
||||
data: backups,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["backups"],
|
||||
queryFn: async () => {
|
||||
const { data } =
|
||||
await apiClient.extension.backup.listmigrationHaloRunV1alpha1Backup({
|
||||
sort: ["metadata.creationTimestamp,desc"],
|
||||
});
|
||||
return data;
|
||||
},
|
||||
refetchInterval(data) {
|
||||
const deletingBackups = data?.items.filter((backup) => {
|
||||
return !!backup.metadata.deletionTimestamp;
|
||||
});
|
||||
|
||||
if (deletingBackups?.length) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
const pendingBackups = data?.items.filter((backup) => {
|
||||
return (
|
||||
backup.status?.phase === BackupStatusPhaseEnum.Pending ||
|
||||
backup.status?.phase === BackupStatusPhaseEnum.Running
|
||||
);
|
||||
});
|
||||
|
||||
if (pendingBackups?.length) {
|
||||
return 3000;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
});
|
||||
const { data: backups, isLoading, isFetching, refetch } = useBackupFetch();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
Toast,
|
||||
VAlert,
|
||||
VButton,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
VTabItem,
|
||||
VTabs,
|
||||
|
@ -15,8 +16,18 @@ import axios from "axios";
|
|||
import { computed } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useBackupFetch } from "../composables/use-backup";
|
||||
import BackupListItem from "../components/BackupListItem.vue";
|
||||
import type { Backup } from "packages/api-client/dist";
|
||||
|
||||
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 showUploader = ref(false);
|
||||
|
@ -58,6 +69,22 @@ const { isLoading: downloading, mutate: handleRemoteDownload } = useMutation({
|
|||
},
|
||||
});
|
||||
|
||||
function handleRestoreFromBackup(backup: Backup) {
|
||||
Dialog.info({
|
||||
title: t("core.backup.operations.restore_by_backup.title"),
|
||||
confirmText: t("core.common.buttons.confirm"),
|
||||
showCancel: false,
|
||||
async onConfirm() {
|
||||
await apiClient.migration.restoreBackup({
|
||||
backupName: backup.metadata.name,
|
||||
});
|
||||
setTimeout(() => {
|
||||
onProcessCompleted();
|
||||
}, 200);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
useQuery({
|
||||
queryKey: ["check-health"],
|
||||
queryFn: async () => {
|
||||
|
@ -142,6 +169,34 @@ useQuery({
|
|||
</VButton>
|
||||
</div>
|
||||
</VTabItem>
|
||||
<VTabItem
|
||||
id="backups"
|
||||
:label="$t('core.backup.restore.tabs.backup.label')"
|
||||
>
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100 overflow-hidden rounded-base"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(backup, index) in normalBackups" :key="index">
|
||||
<BackupListItem :show-operations="false" :backup="backup">
|
||||
<template #end>
|
||||
<VEntityField v-permission="['system:themes:manage']">
|
||||
<template #description>
|
||||
<VButton
|
||||
size="sm"
|
||||
@click="handleRestoreFromBackup(backup)"
|
||||
>
|
||||
{{
|
||||
$t("core.backup.operations.restore_by_backup.button")
|
||||
}}
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
</BackupListItem>
|
||||
</li>
|
||||
</ul>
|
||||
</VTabItem>
|
||||
</VTabs>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue