mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-04 22:40:11 +00:00
fix(backup): verify remote deletes and validate archives
This commit is contained in:
+4
-1
@@ -2122,7 +2122,10 @@ export default function App() {
|
||||
return backupActions.downloadRemoteBackup(hash, destinationId, path, onProgress);
|
||||
},
|
||||
onInspectRemoteBackup: backupActions.inspectRemoteBackup,
|
||||
onDeleteRemoteBackup: backupActions.deleteRemoteBackup,
|
||||
onDeleteRemoteBackup: async (masterPassword: string, destinationId: string, path: string) => {
|
||||
const hash = await deriveCurrentMasterPasswordHash(masterPassword);
|
||||
return backupActions.deleteRemoteBackup(hash, destinationId, path);
|
||||
},
|
||||
onRestoreRemoteBackup: async (masterPassword: string, destinationId: string, path: string, replaceExisting?: boolean) => {
|
||||
const hash = await deriveCurrentMasterPasswordHash(masterPassword);
|
||||
return backupActions.restoreRemoteBackup(hash, destinationId, path, replaceExisting);
|
||||
|
||||
@@ -169,7 +169,7 @@ export interface AppMainRoutesProps {
|
||||
onListRemoteBackups: (destinationId: string, path: string) => Promise<RemoteBackupBrowserResponse>;
|
||||
onDownloadRemoteBackup: (masterPassword: string, destinationId: string, path: string, onProgress?: (percent: number | null) => void) => Promise<void>;
|
||||
onInspectRemoteBackup: (destinationId: string, path: string) => Promise<{ object: 'backup-remote-integrity'; destinationId: string; path: string; fileName: string; integrity: { hasChecksumPrefix: boolean; expectedPrefix: string | null; actualPrefix: string; matches: boolean } }>;
|
||||
onDeleteRemoteBackup: (destinationId: string, path: string) => Promise<void>;
|
||||
onDeleteRemoteBackup: (masterPassword: string, destinationId: string, path: string) => Promise<void>;
|
||||
onRestoreRemoteBackup: (masterPassword: string, destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onRestoreRemoteBackupAllowingChecksumMismatch: (masterPassword: string, destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ interface BackupCenterPageProps {
|
||||
onListRemoteBackups: (destinationId: string, path: string) => Promise<RemoteBackupBrowserResponse>;
|
||||
onDownloadRemoteBackup: (masterPassword: string, destinationId: string, path: string, onProgress?: (percent: number | null) => void) => Promise<void>;
|
||||
onInspectRemoteBackup: (destinationId: string, path: string) => Promise<{ object: 'backup-remote-integrity'; destinationId: string; path: string; fileName: string; integrity: BackupFileIntegrityCheckResult }>;
|
||||
onDeleteRemoteBackup: (destinationId: string, path: string) => Promise<void>;
|
||||
onDeleteRemoteBackup: (masterPassword: string, destinationId: string, path: string) => Promise<void>;
|
||||
onRestoreRemoteBackup: (masterPassword: string, destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onRestoreRemoteBackupAllowingChecksumMismatch: (masterPassword: string, destinationId: string, path: string, replaceExisting?: boolean) => Promise<AdminBackupImportResponse>;
|
||||
onNotify: (type: 'success' | 'error' | 'warning', text: string) => void;
|
||||
@@ -60,6 +60,7 @@ type PendingBackupVerification =
|
||||
| { action: 'import'; replaceExisting: boolean; allowChecksumMismatch: boolean; knownIntegrity?: BackupFileIntegrityCheckResult }
|
||||
| { action: 'runRemoteBackup' }
|
||||
| { action: 'downloadRemote'; path: string }
|
||||
| { action: 'deleteRemote'; destinationId: string; path: string }
|
||||
| { action: 'restoreRemote'; path: string; replaceExisting: boolean; allowChecksumMismatch: boolean; knownIntegrity?: BackupFileIntegrityCheckResult };
|
||||
|
||||
interface BackupProgressPhase {
|
||||
@@ -204,6 +205,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
const [confirmRemoteDeleteOpen, setConfirmRemoteDeleteOpen] = useState(false);
|
||||
const [pendingBackupVerification, setPendingBackupVerification] = useState<PendingBackupVerification | null>(null);
|
||||
const [backupPasswordValue, setBackupPasswordValue] = useState('');
|
||||
const [backupPasswordError, setBackupPasswordError] = useState('');
|
||||
const [backupPasswordSubmitting, setBackupPasswordSubmitting] = useState(false);
|
||||
const [pendingRestoreIntegrity, setPendingRestoreIntegrity] = useState<PendingRestoreIntegrity | null>(null);
|
||||
const [pendingRemoteRestorePath, setPendingRemoteRestorePath] = useState('');
|
||||
@@ -245,11 +247,29 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
? t('txt_backup_save_settings')
|
||||
: pendingBackupVerification?.action === 'runRemoteBackup'
|
||||
? t('txt_backup_run_manual')
|
||||
: pendingBackupVerification?.action === 'downloadRemote'
|
||||
? t('txt_backup_remote_download')
|
||||
: pendingBackupVerification?.action === 'restoreRemote'
|
||||
? t('txt_backup_import')
|
||||
: t('txt_backup_import');
|
||||
: pendingBackupVerification?.action === 'downloadRemote'
|
||||
? t('txt_backup_remote_download')
|
||||
: pendingBackupVerification?.action === 'deleteRemote'
|
||||
? t('txt_delete')
|
||||
: pendingBackupVerification?.action === 'restoreRemote'
|
||||
? t('txt_backup_import')
|
||||
: t('txt_backup_import');
|
||||
|
||||
function openBackupPasswordPrompt(request: PendingBackupVerification): void {
|
||||
setPendingBackupVerification(request);
|
||||
setBackupPasswordValue('');
|
||||
setBackupPasswordError('');
|
||||
}
|
||||
|
||||
function showActionError(error: unknown, fallback: string): string {
|
||||
const message = error instanceof Error ? error.message : fallback;
|
||||
setLocalError(message);
|
||||
if (backupPasswordSubmitting || pendingBackupVerification) {
|
||||
setBackupPasswordError(message);
|
||||
}
|
||||
props.onNotify('error', message);
|
||||
return message;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -502,12 +522,11 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
destinations: (savedSettings?.destinations || []).filter((destination) => destination.id !== destinationIdToDelete),
|
||||
};
|
||||
|
||||
setPendingBackupVerification({ action: 'deleteDestination', destinationId: destinationIdToDelete, settings: nextSettings });
|
||||
setBackupPasswordValue('');
|
||||
openBackupPasswordPrompt({ action: 'deleteDestination', destinationId: destinationIdToDelete, settings: nextSettings });
|
||||
setConfirmDeleteDestinationOpen(false);
|
||||
}
|
||||
|
||||
async function executeDeleteDestination(masterPassword: string, destinationIdToDelete: string, payload: AdminBackupSettings) {
|
||||
async function executeDeleteDestination(masterPassword: string, destinationIdToDelete: string, payload: AdminBackupSettings): Promise<boolean> {
|
||||
setSavingSettings(true);
|
||||
setLocalError('');
|
||||
try {
|
||||
@@ -527,10 +546,10 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
setSelectedDestinationId(nextSelected);
|
||||
setConfirmDeleteDestinationOpen(false);
|
||||
props.onNotify('success', t('txt_backup_destination_deleted'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_settings_save_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_settings_save_failed'));
|
||||
return false;
|
||||
} finally {
|
||||
setSavingSettings(false);
|
||||
}
|
||||
@@ -538,22 +557,21 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
|
||||
async function handleExport() {
|
||||
if (exporting) return;
|
||||
setPendingBackupVerification({ action: 'export' });
|
||||
setBackupPasswordValue('');
|
||||
openBackupPasswordPrompt({ action: 'export' });
|
||||
}
|
||||
|
||||
async function executeExport(masterPassword: string) {
|
||||
async function executeExport(masterPassword: string): Promise<boolean> {
|
||||
setLocalError('');
|
||||
setExporting(true);
|
||||
try {
|
||||
startRestoreProgress('backup-export', t('txt_backup_export'), { source: 'local', includeAttachments: exportIncludeAttachments });
|
||||
await props.onExport(masterPassword, exportIncludeAttachments);
|
||||
props.onNotify('success', t('txt_backup_export_success'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_export_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_export_failed'));
|
||||
window.setTimeout(() => clearRestoreProgress(), 1200);
|
||||
return false;
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
@@ -571,13 +589,12 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
props.onNotify('error', message);
|
||||
return;
|
||||
}
|
||||
setPendingBackupVerification({
|
||||
openBackupPasswordPrompt({
|
||||
action: 'import',
|
||||
replaceExisting,
|
||||
allowChecksumMismatch,
|
||||
knownIntegrity,
|
||||
});
|
||||
setBackupPasswordValue('');
|
||||
}
|
||||
|
||||
async function executeLocalRestore(
|
||||
@@ -585,13 +602,14 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
replaceExisting: boolean,
|
||||
allowChecksumMismatch: boolean = false,
|
||||
knownIntegrity?: BackupFileIntegrityCheckResult
|
||||
) {
|
||||
if (importing) return;
|
||||
): Promise<boolean> {
|
||||
if (importing) return false;
|
||||
if (!selectedFile) {
|
||||
const message = t('txt_backup_file_required');
|
||||
setLocalError(message);
|
||||
setBackupPasswordError(message);
|
||||
props.onNotify('error', message);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
setLocalError('');
|
||||
setConfirmLocalRestoreOpen(false);
|
||||
@@ -614,17 +632,17 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
setConfirmLocalRestoreOpen(false);
|
||||
setConfirmReplaceOpen(false);
|
||||
resetPendingIntegrityWarning();
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (!replaceExisting && isReplaceRequiredError(error)) {
|
||||
clearRestoreProgress();
|
||||
setConfirmLocalRestoreOpen(false);
|
||||
setConfirmReplaceOpen(true);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_restore_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_restore_failed'));
|
||||
window.setTimeout(() => clearRestoreProgress(), 1200);
|
||||
return false;
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
@@ -632,11 +650,10 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
|
||||
async function handleSaveSettings() {
|
||||
if (savingSettings) return;
|
||||
setPendingBackupVerification({ action: 'saveSettings' });
|
||||
setBackupPasswordValue('');
|
||||
openBackupPasswordPrompt({ action: 'saveSettings' });
|
||||
}
|
||||
|
||||
async function executeSaveSettings(masterPassword: string) {
|
||||
async function executeSaveSettings(masterPassword: string): Promise<boolean> {
|
||||
const payload = buildSettingsPayloadForSelectedDestination();
|
||||
const destinationIdToInvalidate = selectedDestinationId;
|
||||
setSavingSettings(true);
|
||||
@@ -656,10 +673,10 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
}
|
||||
setSelectedDestinationId(nextSelected);
|
||||
props.onNotify('success', t('txt_backup_settings_saved'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_settings_save_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_settings_save_failed'));
|
||||
return false;
|
||||
} finally {
|
||||
setSavingSettings(false);
|
||||
}
|
||||
@@ -678,12 +695,11 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
|
||||
async function handleRunRemoteBackup() {
|
||||
if (!selectedDestination || runningRemoteBackup) return;
|
||||
setPendingBackupVerification({ action: 'runRemoteBackup' });
|
||||
setBackupPasswordValue('');
|
||||
openBackupPasswordPrompt({ action: 'runRemoteBackup' });
|
||||
}
|
||||
|
||||
async function executeRunRemoteBackup(masterPassword: string) {
|
||||
if (!selectedDestination) return;
|
||||
async function executeRunRemoteBackup(masterPassword: string): Promise<boolean> {
|
||||
if (!selectedDestination) return false;
|
||||
setRunningRemoteBackup(true);
|
||||
setLocalError('');
|
||||
try {
|
||||
@@ -697,32 +713,31 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
setSelectedDestinationId(selectedDestination.id);
|
||||
await loadRemoteBrowser(selectedDestination.id, currentRemoteBrowserPath, { force: true });
|
||||
props.onNotify('success', t('txt_backup_remote_run_success_verified', { name: result.result.fileName }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_remote_run_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_remote_run_failed'));
|
||||
window.setTimeout(() => clearRestoreProgress(), 1200);
|
||||
return false;
|
||||
} finally {
|
||||
setRunningRemoteBackup(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadRemote(path: string) {
|
||||
setPendingBackupVerification({ action: 'downloadRemote', path });
|
||||
setBackupPasswordValue('');
|
||||
openBackupPasswordPrompt({ action: 'downloadRemote', path });
|
||||
}
|
||||
|
||||
async function executeDownloadRemote(masterPassword: string, path: string) {
|
||||
if (!savedSelectedDestination) return;
|
||||
async function executeDownloadRemote(masterPassword: string, path: string): Promise<boolean> {
|
||||
if (!savedSelectedDestination) return false;
|
||||
setDownloadingRemotePath(path);
|
||||
setDownloadingRemotePercent(null);
|
||||
setLocalError('');
|
||||
try {
|
||||
await props.onDownloadRemoteBackup(masterPassword, savedSelectedDestination.id, path, setDownloadingRemotePercent);
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_remote_download_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_remote_download_failed'));
|
||||
return false;
|
||||
} finally {
|
||||
setDownloadingRemotePath('');
|
||||
setDownloadingRemotePercent(null);
|
||||
@@ -732,18 +747,24 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
async function handleDeleteRemote(path: string) {
|
||||
if (deletingRemotePath) return;
|
||||
if (!savedSelectedDestination) return;
|
||||
openBackupPasswordPrompt({ action: 'deleteRemote', destinationId: savedSelectedDestination.id, path });
|
||||
setConfirmRemoteDeleteOpen(false);
|
||||
}
|
||||
|
||||
async function executeDeleteRemote(masterPassword: string, destinationId: string, path: string): Promise<boolean> {
|
||||
if (deletingRemotePath) return false;
|
||||
setDeletingRemotePath(path);
|
||||
setLocalError('');
|
||||
try {
|
||||
await props.onDeleteRemoteBackup(savedSelectedDestination.id, path);
|
||||
await props.onDeleteRemoteBackup(masterPassword, destinationId, path);
|
||||
setConfirmRemoteDeleteOpen(false);
|
||||
setPendingRemoteDeletePath('');
|
||||
await loadRemoteBrowser(savedSelectedDestination.id, currentRemoteBrowserPath, { force: true });
|
||||
await loadRemoteBrowser(destinationId, remoteBrowserPathByDestination[destinationId] || '', { force: true });
|
||||
props.onNotify('success', t('txt_backup_remote_delete_success'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_remote_delete_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_remote_delete_failed'));
|
||||
return false;
|
||||
} finally {
|
||||
setDeletingRemotePath('');
|
||||
}
|
||||
@@ -802,14 +823,13 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
) {
|
||||
if (restoringRemotePath) return;
|
||||
if (!savedSelectedDestination) return;
|
||||
setPendingBackupVerification({
|
||||
openBackupPasswordPrompt({
|
||||
action: 'restoreRemote',
|
||||
path,
|
||||
replaceExisting,
|
||||
allowChecksumMismatch,
|
||||
knownIntegrity,
|
||||
});
|
||||
setBackupPasswordValue('');
|
||||
}
|
||||
|
||||
async function executeRemoteRestore(
|
||||
@@ -818,9 +838,9 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
replaceExisting: boolean,
|
||||
allowChecksumMismatch: boolean = false,
|
||||
knownIntegrity?: BackupFileIntegrityCheckResult
|
||||
) {
|
||||
if (restoringRemotePath) return;
|
||||
if (!savedSelectedDestination) return;
|
||||
): Promise<boolean> {
|
||||
if (restoringRemotePath) return false;
|
||||
if (!savedSelectedDestination) return false;
|
||||
setConfirmRemoteReplaceOpen(false);
|
||||
setConfirmIntegrityWarningOpen(false);
|
||||
setRestoringRemotePath(path);
|
||||
@@ -840,17 +860,17 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
const skippedMessage = buildSkippedImportMessage(result);
|
||||
if (skippedMessage) props.onNotify('warning', skippedMessage);
|
||||
resetPendingIntegrityWarning();
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (!replaceExisting && isReplaceRequiredError(error)) {
|
||||
setPendingRemoteRestorePath(path);
|
||||
setConfirmRemoteReplaceOpen(true);
|
||||
clearRestoreProgress();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : t('txt_backup_remote_restore_failed');
|
||||
setLocalError(message);
|
||||
props.onNotify('error', message);
|
||||
showActionError(error, t('txt_backup_remote_restore_failed'));
|
||||
window.setTimeout(() => clearRestoreProgress(), 1200);
|
||||
return false;
|
||||
} finally {
|
||||
setRestoringRemotePath('');
|
||||
}
|
||||
@@ -861,31 +881,38 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
const masterPassword = backupPasswordValue;
|
||||
if (!request || backupPasswordSubmitting) return;
|
||||
if (!masterPassword.trim()) {
|
||||
props.onNotify('error', t('txt_master_password_is_required'));
|
||||
setBackupPasswordError(t('txt_master_password_is_required'));
|
||||
return;
|
||||
}
|
||||
setBackupPasswordSubmitting(true);
|
||||
setPendingBackupVerification(null);
|
||||
setBackupPasswordValue('');
|
||||
setBackupPasswordError('');
|
||||
let succeeded = false;
|
||||
try {
|
||||
if (request.action === 'export') {
|
||||
await executeExport(masterPassword);
|
||||
succeeded = await executeExport(masterPassword);
|
||||
} else if (request.action === 'saveSettings') {
|
||||
await executeSaveSettings(masterPassword);
|
||||
succeeded = await executeSaveSettings(masterPassword);
|
||||
} else if (request.action === 'deleteDestination') {
|
||||
await executeDeleteDestination(masterPassword, request.destinationId, request.settings);
|
||||
succeeded = await executeDeleteDestination(masterPassword, request.destinationId, request.settings);
|
||||
} else if (request.action === 'import') {
|
||||
await executeLocalRestore(masterPassword, request.replaceExisting, request.allowChecksumMismatch, request.knownIntegrity);
|
||||
succeeded = await executeLocalRestore(masterPassword, request.replaceExisting, request.allowChecksumMismatch, request.knownIntegrity);
|
||||
} else if (request.action === 'runRemoteBackup') {
|
||||
await executeRunRemoteBackup(masterPassword);
|
||||
succeeded = await executeRunRemoteBackup(masterPassword);
|
||||
} else if (request.action === 'downloadRemote') {
|
||||
await executeDownloadRemote(masterPassword, request.path);
|
||||
succeeded = await executeDownloadRemote(masterPassword, request.path);
|
||||
} else if (request.action === 'deleteRemote') {
|
||||
succeeded = await executeDeleteRemote(masterPassword, request.destinationId, request.path);
|
||||
} else if (request.action === 'restoreRemote') {
|
||||
await executeRemoteRestore(masterPassword, request.path, request.replaceExisting, request.allowChecksumMismatch, request.knownIntegrity);
|
||||
succeeded = await executeRemoteRestore(masterPassword, request.path, request.replaceExisting, request.allowChecksumMismatch, request.knownIntegrity);
|
||||
}
|
||||
} finally {
|
||||
setBackupPasswordSubmitting(false);
|
||||
}
|
||||
if (succeeded) {
|
||||
setPendingBackupVerification(null);
|
||||
setBackupPasswordValue('');
|
||||
setBackupPasswordError('');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -1031,17 +1058,27 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
|
||||
if (backupPasswordSubmitting) return;
|
||||
setPendingBackupVerification(null);
|
||||
setBackupPasswordValue('');
|
||||
setBackupPasswordError('');
|
||||
}}
|
||||
>
|
||||
<label className="field">
|
||||
<span>{t('txt_master_password')}</span>
|
||||
<input
|
||||
id="backup-master-password"
|
||||
className="input"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={backupPasswordValue}
|
||||
onInput={(event) => setBackupPasswordValue((event.currentTarget as HTMLInputElement).value)}
|
||||
aria-invalid={!!backupPasswordError}
|
||||
aria-describedby={backupPasswordError ? 'backup-master-password-error' : undefined}
|
||||
onInput={(event) => {
|
||||
setBackupPasswordValue((event.currentTarget as HTMLInputElement).value);
|
||||
if (backupPasswordError) setBackupPasswordError('');
|
||||
}}
|
||||
/>
|
||||
{backupPasswordError ? (
|
||||
<div id="backup-master-password-error" className="local-error" role="alert">{backupPasswordError}</div>
|
||||
) : null}
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@ export default function useBackupActions(options: UseBackupActionsOptions) {
|
||||
return inspectRemoteBackupIntegrity(authedFetch, destinationId, path);
|
||||
},
|
||||
|
||||
async deleteRemoteBackup(destinationId: string, path: string) {
|
||||
await deleteRemoteBackup(authedFetch, destinationId, path);
|
||||
async deleteRemoteBackup(masterPasswordHash: string, destinationId: string, path: string) {
|
||||
await deleteRemoteBackup(authedFetch, masterPasswordHash, destinationId, path);
|
||||
},
|
||||
|
||||
async restoreRemoteBackup(masterPasswordHash: string, destinationId: string, path: string, replaceExisting: boolean = false) {
|
||||
|
||||
@@ -403,13 +403,15 @@ export async function verifyBackupFileIntegrity(bytes: Uint8Array, fileName: str
|
||||
|
||||
export async function deleteRemoteBackup(
|
||||
authedFetch: AuthedFetch,
|
||||
masterPasswordHash: string,
|
||||
destinationId: string,
|
||||
path: string
|
||||
): Promise<void> {
|
||||
const params = new URLSearchParams();
|
||||
params.set('destinationId', destinationId);
|
||||
params.set('path', path);
|
||||
const resp = await authedFetch(`/api/admin/backup/remote/file?${params.toString()}`, { method: 'DELETE' });
|
||||
const resp = await authedFetch('/api/admin/backup/remote/file', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ destinationId, path, masterPasswordHash }),
|
||||
});
|
||||
if (!resp.ok) throw new Error(await parseErrorMessage(resp, t('txt_backup_remote_delete_failed')));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user