mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: enhance backup import functionality to handle skipped items and provide detailed feedback
This commit is contained in:
@@ -2,7 +2,7 @@ import { lazy, Suspense } from 'preact/compat';
|
||||
import { Link, Route, Switch } from 'wouter';
|
||||
import { ArrowUpDown, Cloud, LogOut, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact';
|
||||
import type { ImportAttachmentFile, ImportResultSummary } from '@/components/ImportPage';
|
||||
import type { AdminBackupRunResponse, AdminBackupSettings, RemoteBackupBrowserResponse } from '@/lib/api/backup';
|
||||
import type { AdminBackupImportResponse, AdminBackupRunResponse, AdminBackupSettings, RemoteBackupBrowserResponse } from '@/lib/api/backup';
|
||||
import type { CiphersImportPayload } from '@/lib/api/vault';
|
||||
import { t } from '@/lib/i18n';
|
||||
import type { AdminInvite, AdminUser, AuthorizedDevice, Cipher, Folder as VaultFolder, Profile, Send, SendDraft, SessionState, VaultDraft } from '@/lib/types';
|
||||
@@ -88,14 +88,14 @@ export interface AppMainRoutesProps {
|
||||
onDeleteUser: (userId: string) => Promise<void>;
|
||||
onRevokeInvite: (code: string) => Promise<void>;
|
||||
onExportBackup: () => Promise<void>;
|
||||
onImportBackup: (file: File, replaceExisting?: boolean) => Promise<void>;
|
||||
onImportBackup: (file: File, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onLoadBackupSettings: () => Promise<AdminBackupSettings>;
|
||||
onSaveBackupSettings: (settings: AdminBackupSettings) => Promise<AdminBackupSettings>;
|
||||
onRunRemoteBackup: (destinationId?: string | null) => Promise<AdminBackupRunResponse>;
|
||||
onListRemoteBackups: (destinationId: string, path: string) => Promise<RemoteBackupBrowserResponse>;
|
||||
onDownloadRemoteBackup: (destinationId: string, path: string, onProgress?: (percent: number | null) => void) => Promise<void>;
|
||||
onDeleteRemoteBackup: (destinationId: string, path: string) => Promise<void>;
|
||||
onRestoreRemoteBackup: (destinationId: string, path: string, replaceExisting?: boolean) => Promise<void>;
|
||||
onRestoreRemoteBackup: (destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
}
|
||||
|
||||
export default function AppMainRoutes(props: AppMainRoutesProps) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||
import {
|
||||
type AdminBackupImportResponse,
|
||||
type AdminBackupRunResponse,
|
||||
type AdminBackupSettings,
|
||||
type BackupDestinationRecord,
|
||||
@@ -30,15 +31,25 @@ import { BackupOperationsSidebar } from './backup-center/BackupOperationsSidebar
|
||||
interface BackupCenterPageProps {
|
||||
currentUserId: string | null;
|
||||
onExport: () => Promise<void>;
|
||||
onImport: (file: File, replaceExisting?: boolean) => Promise<void>;
|
||||
onImport: (file: File, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onLoadSettings: () => Promise<AdminBackupSettings>;
|
||||
onSaveSettings: (settings: AdminBackupSettings) => Promise<AdminBackupSettings>;
|
||||
onRunRemoteBackup: (destinationId?: string | null) => Promise<AdminBackupRunResponse>;
|
||||
onListRemoteBackups: (destinationId: string, path: string) => Promise<RemoteBackupBrowserResponse>;
|
||||
onDownloadRemoteBackup: (destinationId: string, path: string, onProgress?: (percent: number | null) => void) => Promise<void>;
|
||||
onDeleteRemoteBackup: (destinationId: string, path: string) => Promise<void>;
|
||||
onRestoreRemoteBackup: (destinationId: string, path: string, replaceExisting?: boolean) => Promise<void>;
|
||||
onNotify: (type: 'success' | 'error', text: string) => void;
|
||||
onRestoreRemoteBackup: (destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onNotify: (type: 'success' | 'error' | 'warning', text: string) => void;
|
||||
}
|
||||
|
||||
function buildSkippedImportMessage(result: AdminBackupImportResponse): string | null {
|
||||
const skipped = result.skipped;
|
||||
if (!skipped || (!skipped.attachments && !skipped.sendFiles)) return null;
|
||||
return t('txt_backup_restore_skipped_summary', {
|
||||
reason: skipped.reason || t('txt_backup_restore_skipped_reason_default'),
|
||||
attachments: String(skipped.attachments),
|
||||
sendFiles: String(skipped.sendFiles),
|
||||
});
|
||||
}
|
||||
|
||||
export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
@@ -286,8 +297,10 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
setLocalError('');
|
||||
setImporting(true);
|
||||
try {
|
||||
await props.onImport(selectedFile, replaceExisting);
|
||||
const result = await props.onImport(selectedFile, replaceExisting);
|
||||
props.onNotify('success', t('txt_backup_restore_success_relogin'));
|
||||
const skippedMessage = buildSkippedImportMessage(result);
|
||||
if (skippedMessage) props.onNotify('warning', skippedMessage);
|
||||
resetSelectedFile();
|
||||
setConfirmLocalRestoreOpen(false);
|
||||
setConfirmReplaceOpen(false);
|
||||
@@ -406,10 +419,12 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
setRestoringRemotePath(path);
|
||||
setLocalError('');
|
||||
try {
|
||||
await props.onRestoreRemoteBackup(savedSelectedDestination.id, path, replaceExisting);
|
||||
const result = await props.onRestoreRemoteBackup(savedSelectedDestination.id, path, replaceExisting);
|
||||
setConfirmRemoteReplaceOpen(false);
|
||||
setPendingRemoteRestorePath('');
|
||||
props.onNotify('success', t('txt_backup_restore_success_relogin'));
|
||||
const skippedMessage = buildSkippedImportMessage(result);
|
||||
if (skippedMessage) props.onNotify('warning', skippedMessage);
|
||||
} catch (error) {
|
||||
if (!replaceExisting && isReplaceRequiredError(error)) {
|
||||
setPendingRemoteRestorePath(path);
|
||||
|
||||
@@ -30,8 +30,9 @@ export default function useBackupActions(options: UseBackupActionsOptions) {
|
||||
},
|
||||
|
||||
async importBackup(file: File, replaceExisting: boolean = false) {
|
||||
await importAdminBackup(authedFetch, file, replaceExisting);
|
||||
const result = await importAdminBackup(authedFetch, file, replaceExisting);
|
||||
onImported?.();
|
||||
return result;
|
||||
},
|
||||
|
||||
async loadSettings() {
|
||||
@@ -60,8 +61,9 @@ export default function useBackupActions(options: UseBackupActionsOptions) {
|
||||
},
|
||||
|
||||
async restoreRemoteBackup(destinationId: string, path: string, replaceExisting: boolean = false) {
|
||||
await restoreRemoteBackup(authedFetch, destinationId, path, replaceExisting);
|
||||
const result = await restoreRemoteBackup(authedFetch, destinationId, path, replaceExisting);
|
||||
onRestored?.();
|
||||
return result;
|
||||
},
|
||||
}),
|
||||
[authedFetch, onImported, onRestored]
|
||||
|
||||
@@ -86,9 +86,23 @@ export interface AdminBackupImportCounts {
|
||||
sendFiles: number;
|
||||
}
|
||||
|
||||
export interface AdminBackupImportSkippedItem {
|
||||
kind: 'attachment' | 'send-file';
|
||||
path: string;
|
||||
sizeBytes: number;
|
||||
}
|
||||
|
||||
export interface AdminBackupImportSkipped {
|
||||
reason: string | null;
|
||||
attachments: number;
|
||||
sendFiles: number;
|
||||
items: AdminBackupImportSkippedItem[];
|
||||
}
|
||||
|
||||
export interface AdminBackupImportResponse {
|
||||
object: 'instance-backup-import';
|
||||
imported: AdminBackupImportCounts;
|
||||
skipped: AdminBackupImportSkipped;
|
||||
}
|
||||
|
||||
export interface AdminBackupExportPayload {
|
||||
|
||||
@@ -25,6 +25,8 @@ const messages: Record<Locale, Record<string, string>> = {
|
||||
txt_backup_export_success: "Backup exported",
|
||||
txt_backup_import_success_relogin: "Backup restored. Please sign in again.",
|
||||
txt_backup_restore_success_relogin: "Backup restored. Please sign in again.",
|
||||
txt_backup_restore_skipped_summary: "{reason}. Skipped {attachments} attachment(s) and {sendFiles} Send file(s).",
|
||||
txt_backup_restore_skipped_reason_default: "Some files could not be restored",
|
||||
txt_backup_export_failed: "Backup export failed",
|
||||
txt_backup_import_failed: "Backup restore failed",
|
||||
txt_backup_restore_failed: "Backup restore failed",
|
||||
@@ -606,6 +608,8 @@ const zhCNOverrides: Record<string, string> = {
|
||||
txt_backup_export_success: '备份已导出',
|
||||
txt_backup_import_success_relogin: '备份已还原,请重新登录',
|
||||
txt_backup_restore_success_relogin: '备份已还原,请重新登录',
|
||||
txt_backup_restore_skipped_summary: '{reason},已跳过 {attachments} 个附件和 {sendFiles} 个 Send 文件',
|
||||
txt_backup_restore_skipped_reason_default: '部分文件无法还原',
|
||||
txt_backup_export_failed: '备份导出失败',
|
||||
txt_backup_import_failed: '备份还原失败',
|
||||
txt_backup_restore_failed: '备份还原失败',
|
||||
|
||||
Reference in New Issue
Block a user