feat(backup): add local backup import flow (#5669)

* feat(backup): add local backup import flow

* refactor(backup): robustify history listing and propagate import error details

* docs: Changelog.md
This commit is contained in:
Sline
2025-12-19 17:46:31 +08:00
committed by GitHub
parent b4e25951b4
commit bd8eccdcea
22 changed files with 240 additions and 29 deletions

View File

@@ -8,6 +8,7 @@
<summary><strong> ✨ 新增功能 </strong></summary> <summary><strong> ✨ 新增功能 </strong></summary>
- 允许代理页面允许高级过滤搜索 - 允许代理页面允许高级过滤搜索
- 备份设置页面新增导入备份按钮
</details> </details>

View File

@@ -27,6 +27,12 @@ pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
feat::restore_local_backup(filename).await.stringify_err() feat::restore_local_backup(filename).await.stringify_err()
} }
/// Import local backup into the app's backup directory
#[tauri::command]
pub async fn import_local_backup(source: String) -> CmdResult<String> {
feat::import_local_backup(source).await.stringify_err()
}
/// Export local backup to a user selected destination /// Export local backup to a user selected destination
#[tauri::command] #[tauri::command]
pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> { pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {

View File

@@ -158,6 +158,53 @@ where
Ok(final_name) Ok(final_name)
} }
/// Import an existing backup file into the local backup directory
pub async fn import_local_backup(source: String) -> Result<String> {
let source_path = PathBuf::from(source.as_str());
if !source_path.exists() {
return Err(anyhow!("Backup file not found: {source}"));
}
if !source_path.is_file() {
return Err(anyhow!("Backup path is not a file: {source}"));
}
let ext = source_path
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_ascii_lowercase())
.unwrap_or_default();
if ext != "zip" {
return Err(anyhow!("Only .zip backup files are supported"));
}
let file_name = source_path
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| anyhow!("Invalid backup file name"))?;
let backup_dir = local_backup_dir()?;
let target_path = backup_dir.join(file_name);
if target_path == source_path {
// Already located in the backup directory
return Ok(file_name.to_string().into());
}
if let Some(parent) = target_path.parent() {
fs::create_dir_all(parent).await?;
}
if target_path.exists() {
return Err(anyhow!("Backup file already exists: {file_name}"));
}
fs::copy(&source_path, &target_path)
.await
.map_err(|err| anyhow!("Failed to import backup file: {err:#?}"))?;
Ok(file_name.to_string().into())
}
async fn move_file(from: PathBuf, to: PathBuf) -> Result<()> { async fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
if let Some(parent) = to.parent() { if let Some(parent) = to.parent() {
fs::create_dir_all(parent).await?; fs::create_dir_all(parent).await?;

View File

@@ -210,6 +210,7 @@ mod app_init {
cmd::list_local_backup, cmd::list_local_backup,
cmd::delete_local_backup, cmd::delete_local_backup,
cmd::restore_local_backup, cmd::restore_local_backup,
cmd::import_local_backup,
cmd::export_local_backup, cmd::export_local_backup,
cmd::create_webdav_backup, cmd::create_webdav_backup,
cmd::save_webdav_config, cmd::save_webdav_config,

View File

@@ -57,7 +57,9 @@ interface BackupHistoryViewerProps {
interface BackupRow { interface BackupRow {
filename: string; filename: string;
platform: string; platform: string;
backup_time: dayjs.Dayjs; backup_time: dayjs.Dayjs | null;
display_time: string;
sort_value: number;
} }
const confirmAsync = async (message: string) => { const confirmAsync = async (message: string) => {
@@ -86,16 +88,44 @@ export const BackupHistoryViewer = ({
const pageSize = 8; const pageSize = 8;
const isBusy = loading || isRestarting; const isBusy = loading || isRestarting;
const buildRow = useCallback((filename: string): BackupRow | null => { const buildRow = useCallback(
const platform = filename.split("-")[0]; (item: ILocalBackupFile | IWebDavFile): BackupRow | null => {
const { filename, last_modified } = item;
if (!filename.toLowerCase().endsWith(".zip")) return null;
const platform =
(filename.includes("-") && filename.split("-")[0]) ||
t("settings.modals.backup.history.unknownPlatform", {
defaultValue: "unknown",
});
const match = filename.match(FILENAME_PATTERN); const match = filename.match(FILENAME_PATTERN);
if (!match) return null; const parsedFromName = match ? dayjs(match[0], DATE_FORMAT, true) : null;
const parsedFromModified =
last_modified && dayjs(last_modified).isValid()
? dayjs(last_modified)
: null;
const backupTime = parsedFromName?.isValid()
? parsedFromName
: parsedFromModified;
return { return {
filename, filename,
platform, platform,
backup_time: dayjs(match[0], DATE_FORMAT), backup_time: backupTime ?? null,
display_time:
backupTime?.format("YYYY-MM-DD HH:mm") ??
parsedFromModified?.format("YYYY-MM-DD HH:mm") ??
t("settings.modals.backup.history.unknownTime", {
defaultValue: "Unknown time",
}),
sort_value:
backupTime?.valueOf() ??
parsedFromModified?.valueOf() ??
Number.NEGATIVE_INFINITY,
}; };
}, []); },
[t],
);
const fetchRows = useCallback(async () => { const fetchRows = useCallback(async () => {
if (!open) return; if (!open) return;
@@ -108,9 +138,13 @@ export const BackupHistoryViewer = ({
const list = isLocal ? await listLocalBackup() : await listWebDavBackup(); const list = isLocal ? await listLocalBackup() : await listWebDavBackup();
setRows( setRows(
list list
.map((item) => buildRow(item.filename)) .map((item) => buildRow(item))
.filter((item): item is BackupRow => item !== null) .filter((item): item is BackupRow => item !== null)
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1)), .sort((a, b) =>
a.sort_value === b.sort_value
? b.filename.localeCompare(a.filename)
: b.sort_value - a.sort_value,
),
); );
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -138,7 +172,8 @@ export const BackupHistoryViewer = ({
return t("settings.modals.backup.manual.webdav"); return t("settings.modals.backup.manual.webdav");
} }
if (!total) return t("settings.modals.backup.history.empty"); if (!total) return t("settings.modals.backup.history.empty");
const recent = rows[0]?.backup_time.fromNow(); const recent =
rows[0]?.backup_time?.fromNow() ?? rows[0]?.display_time ?? "";
return t("settings.modals.backup.history.summary", { return t("settings.modals.backup.history.summary", {
count: total, count: total,
recent, recent,
@@ -283,7 +318,7 @@ export const BackupHistoryViewer = ({
spacing={1.5} spacing={1.5}
> >
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{`${row.platform} · ${row.backup_time.format("YYYY-MM-DD HH:mm")}`} {`${row.platform} · ${row.display_time}`}
</Typography> </Typography>
<Stack <Stack
direction="row" direction="row"

View File

@@ -7,13 +7,18 @@ import {
Stack, Stack,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import type { ReactNode, Ref } from "react"; import type { ReactNode, Ref } from "react";
import { useCallback, useImperativeHandle, useState } from "react"; import { useCallback, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog, DialogRef } from "@/components/base";
import { createLocalBackup, createWebdavBackup } from "@/services/cmds"; import {
createLocalBackup,
createWebdavBackup,
importLocalBackup,
} from "@/services/cmds";
import { showNotice } from "@/services/notice-service"; import { showNotice } from "@/services/notice-service";
import { AutoBackupSettings } from "./auto-backup-settings"; import { AutoBackupSettings } from "./auto-backup-settings";
@@ -26,6 +31,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [busyAction, setBusyAction] = useState<BackupSource | null>(null); const [busyAction, setBusyAction] = useState<BackupSource | null>(null);
const [localImporting, setLocalImporting] = useState(false);
const [historyOpen, setHistoryOpen] = useState(false); const [historyOpen, setHistoryOpen] = useState(false);
const [historySource, setHistorySource] = useState<BackupSource>("local"); const [historySource, setHistorySource] = useState<BackupSource>("local");
const [historyPage, setHistoryPage] = useState(0); const [historyPage, setHistoryPage] = useState(0);
@@ -67,6 +73,28 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
} }
}); });
const handleImport = useLockFn(async () => {
const selected = await openDialog({
multiple: false,
filters: [{ name: "Backup File", extensions: ["zip"] }],
});
if (!selected || Array.isArray(selected)) return;
try {
setLocalImporting(true);
await importLocalBackup(selected);
showNotice.success("settings.modals.backup.messages.localBackupImported");
openHistory("local");
} catch (error) {
console.error(error);
showNotice.error(
"settings.modals.backup.messages.localBackupImportFailed",
{ error },
);
} finally {
setLocalImporting(false);
}
});
const setWebdavBusy = useCallback( const setWebdavBusy = useCallback(
(loading: boolean) => { (loading: boolean) => {
setBusyAction(loading ? "webdav" : null); setBusyAction(loading ? "webdav" : null);
@@ -74,6 +102,8 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
[setBusyAction], [setBusyAction],
); );
const isLocalBusy = busyAction === "local" || localImporting;
return ( return (
<BaseDialog <BaseDialog
open={open} open={open}
@@ -125,6 +155,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="contained" variant="contained"
size="small" size="small"
loading={busyAction === "local"} loading={busyAction === "local"}
disabled={localImporting}
onClick={() => handleBackup("local")} onClick={() => handleBackup("local")}
> >
{t("settings.modals.backup.actions.backup")} {t("settings.modals.backup.actions.backup")}
@@ -133,10 +164,21 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
key="history" key="history"
variant="outlined" variant="outlined"
size="small" size="small"
disabled={isLocalBusy}
onClick={() => openHistory("local")} onClick={() => openHistory("local")}
> >
{t("settings.modals.backup.actions.viewHistory")} {t("settings.modals.backup.actions.viewHistory")}
</Button>, </Button>,
<LoadingButton
key="import"
variant="text"
size="small"
loading={localImporting}
disabled={busyAction === "local"}
onClick={() => handleImport()}
>
{t("settings.modals.backup.actions.importBackup")}
</LoadingButton>,
], ],
}, },
{ {

View File

@@ -283,6 +283,7 @@
"backup": "نسخ احتياطي", "backup": "نسخ احتياطي",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "حذف النسخة الاحتياطية", "deleteBackup": "حذف النسخة الاحتياطية",
"restore": "استعادة", "restore": "استعادة",
"restoreBackup": "استعادة النسخة الاحتياطية", "restoreBackup": "استعادة النسخة الاحتياطية",
@@ -307,6 +308,8 @@
"restoreSuccess": "تمت الاستعادة بنجاح، سيعاد تشغيل التطبيق خلال ثانية واحدة", "restoreSuccess": "تمت الاستعادة بنجاح، سيعاد تشغيل التطبيق خلال ثانية واحدة",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "هل تريد بالتأكيد حذف ملف النسخة الاحتياطية هذا؟", "confirmDelete": "هل تريد بالتأكيد حذف ملف النسخة الاحتياطية هذا؟",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Sichern", "backup": "Sichern",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Sicherung löschen", "deleteBackup": "Sicherung löschen",
"restore": "Wiederherstellen", "restore": "Wiederherstellen",
"restoreBackup": "Sicherung wiederherstellen", "restoreBackup": "Sicherung wiederherstellen",
@@ -307,6 +308,8 @@
"restoreSuccess": "Wiederherstellung erfolgreich. Die App wird in 1 Sekunde neu starten.", "restoreSuccess": "Wiederherstellung erfolgreich. Die App wird in 1 Sekunde neu starten.",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Confirm to delete this backup file?", "confirmDelete": "Confirm to delete this backup file?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Backup", "backup": "Backup",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Delete Backup", "deleteBackup": "Delete Backup",
"restore": "Restore", "restore": "Restore",
"restoreBackup": "Restore Backup", "restoreBackup": "Restore Backup",
@@ -307,6 +308,8 @@
"restoreSuccess": "Restore Success, App will restart in 1s", "restoreSuccess": "Restore Success, App will restart in 1s",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Confirm to delete this backup file?", "confirmDelete": "Confirm to delete this backup file?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Copia de seguridad", "backup": "Copia de seguridad",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Eliminar copia de seguridad", "deleteBackup": "Eliminar copia de seguridad",
"restore": "Restaurar", "restore": "Restaurar",
"restoreBackup": "Restaurar copia de seguridad", "restoreBackup": "Restaurar copia de seguridad",
@@ -307,6 +308,8 @@
"restoreSuccess": "Restauración exitosa. La aplicación se reiniciará en 1 segundo", "restoreSuccess": "Restauración exitosa. La aplicación se reiniciará en 1 segundo",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Confirm to delete this backup file?", "confirmDelete": "Confirm to delete this backup file?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "پشتیبان‌گیری", "backup": "پشتیبان‌گیری",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "حذف پشتیبان", "deleteBackup": "حذف پشتیبان",
"restore": "بازیابی", "restore": "بازیابی",
"restoreBackup": "بازیابی پشتیبان", "restoreBackup": "بازیابی پشتیبان",
@@ -307,6 +308,8 @@
"restoreSuccess": "بازیابی با موفقیت انجام شد، برنامه در 1 ثانیه راه‌اندازی مجدد می‌شود", "restoreSuccess": "بازیابی با موفقیت انجام شد، برنامه در 1 ثانیه راه‌اندازی مجدد می‌شود",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "آیا از حذف این فایل پشتیبان اطمینان دارید؟", "confirmDelete": "آیا از حذف این فایل پشتیبان اطمینان دارید؟",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "تاریخچه پشتیبان گیری", "title": "تاریخچه پشتیبان گیری",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "هیچ نسخه پشتیبان در دسترس نیست" "empty": "هیچ نسخه پشتیبان در دسترس نیست",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "پیکربندی WebDAV" "title": "پیکربندی WebDAV"

View File

@@ -283,6 +283,7 @@
"backup": "Cadangan", "backup": "Cadangan",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Hapus Cadangan", "deleteBackup": "Hapus Cadangan",
"restore": "Pulihkan", "restore": "Pulihkan",
"restoreBackup": "Pulihkan Cadangan", "restoreBackup": "Pulihkan Cadangan",
@@ -307,6 +308,8 @@
"restoreSuccess": "Pemulihan Berhasil, Aplikasi akan dimulai ulang dalam 1 detik", "restoreSuccess": "Pemulihan Berhasil, Aplikasi akan dimulai ulang dalam 1 detik",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Konfirmasi untuk menghapus file cadangan ini?", "confirmDelete": "Konfirmasi untuk menghapus file cadangan ini?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "バックアップ", "backup": "バックアップ",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "バックアップをインポート",
"deleteBackup": "バックアップを削除", "deleteBackup": "バックアップを削除",
"restore": "復元", "restore": "復元",
"restoreBackup": "バックアップを復元", "restoreBackup": "バックアップを復元",
@@ -307,6 +308,8 @@
"restoreSuccess": "復元に成功しました。アプリケーションは1秒後に再起動します。", "restoreSuccess": "復元に成功しました。アプリケーションは1秒後に再起動します。",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "ローカルバックアップのインポートに成功しました",
"localBackupImportFailed": "ローカルバックアップのインポートに失敗しました: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Confirm to delete this backup file?", "confirmDelete": "Confirm to delete this backup file?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "백업", "backup": "백업",
"export": "내보내기", "export": "내보내기",
"exportBackup": "백업 내보내기", "exportBackup": "백업 내보내기",
"importBackup": "백업 가져오기",
"deleteBackup": "백업 삭제", "deleteBackup": "백업 삭제",
"restore": "복원", "restore": "복원",
"restoreBackup": "백업 복원", "restoreBackup": "백업 복원",
@@ -307,6 +308,8 @@
"restoreSuccess": "복원 성공, 1초 후 앱이 재시작됩니다", "restoreSuccess": "복원 성공, 1초 후 앱이 재시작됩니다",
"localBackupExported": "로컬 백업이 내보내졌습니다", "localBackupExported": "로컬 백업이 내보내졌습니다",
"localBackupExportFailed": "로컬 백업 내보내기 실패", "localBackupExportFailed": "로컬 백업 내보내기 실패",
"localBackupImported": "로컬 백업을 가져왔습니다",
"localBackupImportFailed": "로컬 백업 가져오기 실패: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "이 백업 파일을 삭제하시겠습니까?", "confirmDelete": "이 백업 파일을 삭제하시겠습니까?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "알 수 없음",
"unknownTime": "시간 정보 없음"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Резервное копирование", "backup": "Резервное копирование",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Удалить резервную копию", "deleteBackup": "Удалить резервную копию",
"restore": "Восстановить", "restore": "Восстановить",
"restoreBackup": "Восстановить резервную копию", "restoreBackup": "Восстановить резервную копию",
@@ -307,6 +308,8 @@
"restoreSuccess": "Восстановление успешно выполнено, приложение перезапустится через 1 секунду", "restoreSuccess": "Восстановление успешно выполнено, приложение перезапустится через 1 секунду",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Не удалось импортировать локальную резервную копию: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Вы уверены, что хотите удалить этот файл резервной копии?", "confirmDelete": "Вы уверены, что хотите удалить этот файл резервной копии?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "неизвестно",
"unknownTime": "Неизвестное время"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Yedekle", "backup": "Yedekle",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Yedeği Sil", "deleteBackup": "Yedeği Sil",
"restore": "Geri Yükle", "restore": "Geri Yükle",
"restoreBackup": "Yedeği Geri Yükle", "restoreBackup": "Yedeği Geri Yükle",
@@ -307,6 +308,8 @@
"restoreSuccess": "Geri Yükleme Başarılı, Uygulama 1 saniye içinde yeniden başlatılacak", "restoreSuccess": "Geri Yükleme Başarılı, Uygulama 1 saniye içinde yeniden başlatılacak",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Bu yedek dosyasını silmeyi onaylıyor musunuz?", "confirmDelete": "Bu yedek dosyasını silmeyi onaylıyor musunuz?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "Резерв копия", "backup": "Резерв копия",
"export": "Export", "export": "Export",
"exportBackup": "Export Backup", "exportBackup": "Export Backup",
"importBackup": "Import Backup",
"deleteBackup": "Резерв копияне бетерү", "deleteBackup": "Резерв копияне бетерү",
"restore": "Кайтару", "restore": "Кайтару",
"restoreBackup": "Резерв копияне кайтару", "restoreBackup": "Резерв копияне кайтару",
@@ -307,6 +308,8 @@
"restoreSuccess": "Уңышлы кайтарылды, кушымта 1 секундтан яңадан башланачак", "restoreSuccess": "Уңышлы кайтарылды, кушымта 1 секундтан яңадан башланачак",
"localBackupExported": "Local backup exported successfully", "localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup", "localBackupExportFailed": "Failed to export local backup",
"localBackupImported": "Local backup imported successfully",
"localBackupImportFailed": "Failed to import local backup: {{error}}",
"webdavRefreshSuccess": "WebDAV refresh succeeded", "webdavRefreshSuccess": "WebDAV refresh succeeded",
"webdavRefreshFailed": "WebDAV refresh failed: {{error}}", "webdavRefreshFailed": "WebDAV refresh failed: {{error}}",
"confirmDelete": "Бу резерв копия файлын бетерергә телисезме?", "confirmDelete": "Бу резерв копия файлын бетерергә телисезме?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "Backup history", "title": "Backup history",
"summary": "{{count}} backups • latest {{recent}}", "summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available" "empty": "No backups available",
"unknownPlatform": "unknown",
"unknownTime": "Unknown time"
}, },
"webdav": { "webdav": {
"title": "WebDAV settings" "title": "WebDAV settings"

View File

@@ -283,6 +283,7 @@
"backup": "备份", "backup": "备份",
"export": "导出", "export": "导出",
"exportBackup": "导出备份", "exportBackup": "导出备份",
"importBackup": "导入备份",
"deleteBackup": "删除备份", "deleteBackup": "删除备份",
"restore": "恢复", "restore": "恢复",
"restoreBackup": "恢复备份", "restoreBackup": "恢复备份",
@@ -307,6 +308,8 @@
"restoreSuccess": "恢复成功,应用将在 1 秒后重启", "restoreSuccess": "恢复成功,应用将在 1 秒后重启",
"localBackupExported": "本地备份导出成功", "localBackupExported": "本地备份导出成功",
"localBackupExportFailed": "本地备份导出失败", "localBackupExportFailed": "本地备份导出失败",
"localBackupImported": "本地备份导入成功",
"localBackupImportFailed": "本地备份导入失败:{{error}}",
"webdavRefreshSuccess": "WebDAV 刷新成功", "webdavRefreshSuccess": "WebDAV 刷新成功",
"webdavRefreshFailed": "WebDAV 刷新失败: {{error}}", "webdavRefreshFailed": "WebDAV 刷新失败: {{error}}",
"confirmDelete": "确认删除此备份文件吗?", "confirmDelete": "确认删除此备份文件吗?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "备份记录", "title": "备份记录",
"summary": "共 {{count}} 份备份 · 最近 {{recent}}", "summary": "共 {{count}} 份备份 · 最近 {{recent}}",
"empty": "暂无备份记录" "empty": "暂无备份记录",
"unknownPlatform": "未知",
"unknownTime": "未知时间"
}, },
"webdav": { "webdav": {
"title": "WebDAV 设置" "title": "WebDAV 设置"

View File

@@ -283,6 +283,7 @@
"backup": "備份", "backup": "備份",
"export": "匯出", "export": "匯出",
"exportBackup": "匯出備份", "exportBackup": "匯出備份",
"importBackup": "匯入備份",
"deleteBackup": "刪除備份", "deleteBackup": "刪除備份",
"restore": "還原", "restore": "還原",
"restoreBackup": "還原備份", "restoreBackup": "還原備份",
@@ -307,6 +308,8 @@
"restoreSuccess": "還原成功,應用程式將在 1 秒後重啟", "restoreSuccess": "還原成功,應用程式將在 1 秒後重啟",
"localBackupExported": "本機備份匯出成功", "localBackupExported": "本機備份匯出成功",
"localBackupExportFailed": "本機備份匯出失敗", "localBackupExportFailed": "本機備份匯出失敗",
"localBackupImported": "本機備份匯入成功",
"localBackupImportFailed": "本機備份匯入失敗:{{error}}",
"webdavRefreshSuccess": "WebDAV 更新成功", "webdavRefreshSuccess": "WebDAV 更新成功",
"webdavRefreshFailed": "WebDAV 更新失敗: {{error}}", "webdavRefreshFailed": "WebDAV 更新失敗: {{error}}",
"confirmDelete": "確認是否刪除此備份檔案嗎?", "confirmDelete": "確認是否刪除此備份檔案嗎?",
@@ -333,7 +336,9 @@
"history": { "history": {
"title": "備份紀錄", "title": "備份紀錄",
"summary": "共 {{count}} 份備份 · 最近 {{recent}}", "summary": "共 {{count}} 份備份 · 最近 {{recent}}",
"empty": "尚無備份紀錄" "empty": "尚無備份紀錄",
"unknownPlatform": "未知",
"unknownTime": "未知時間"
}, },
"webdav": { "webdav": {
"title": "WebDAV 設定" "title": "WebDAV 設定"

View File

@@ -455,6 +455,10 @@ export async function restoreLocalBackup(filename: string) {
return invoke<void>("restore_local_backup", { filename }); return invoke<void>("restore_local_backup", { filename });
} }
export async function importLocalBackup(source: string) {
return invoke<string>("import_local_backup", { source });
}
export async function exportLocalBackup(filename: string, destination: string) { export async function exportLocalBackup(filename: string, destination: string) {
return invoke<void>("export_local_backup", { filename, destination }); return invoke<void>("export_local_backup", { filename, destination });
} }

View File

@@ -464,6 +464,7 @@ export const translationKeys = [
"settings.modals.backup.actions.backup", "settings.modals.backup.actions.backup",
"settings.modals.backup.actions.export", "settings.modals.backup.actions.export",
"settings.modals.backup.actions.exportBackup", "settings.modals.backup.actions.exportBackup",
"settings.modals.backup.actions.importBackup",
"settings.modals.backup.actions.deleteBackup", "settings.modals.backup.actions.deleteBackup",
"settings.modals.backup.actions.restore", "settings.modals.backup.actions.restore",
"settings.modals.backup.actions.restoreBackup", "settings.modals.backup.actions.restoreBackup",
@@ -484,6 +485,8 @@ export const translationKeys = [
"settings.modals.backup.messages.restoreSuccess", "settings.modals.backup.messages.restoreSuccess",
"settings.modals.backup.messages.localBackupExported", "settings.modals.backup.messages.localBackupExported",
"settings.modals.backup.messages.localBackupExportFailed", "settings.modals.backup.messages.localBackupExportFailed",
"settings.modals.backup.messages.localBackupImported",
"settings.modals.backup.messages.localBackupImportFailed",
"settings.modals.backup.messages.webdavRefreshSuccess", "settings.modals.backup.messages.webdavRefreshSuccess",
"settings.modals.backup.messages.webdavRefreshFailed", "settings.modals.backup.messages.webdavRefreshFailed",
"settings.modals.backup.messages.confirmDelete", "settings.modals.backup.messages.confirmDelete",
@@ -503,6 +506,8 @@ export const translationKeys = [
"settings.modals.backup.history.title", "settings.modals.backup.history.title",
"settings.modals.backup.history.summary", "settings.modals.backup.history.summary",
"settings.modals.backup.history.empty", "settings.modals.backup.history.empty",
"settings.modals.backup.history.unknownPlatform",
"settings.modals.backup.history.unknownTime",
"settings.modals.backup.webdav.title", "settings.modals.backup.webdav.title",
"settings.modals.backup.table.filename", "settings.modals.backup.table.filename",
"settings.modals.backup.table.backupTime", "settings.modals.backup.table.backupTime",

View File

@@ -694,6 +694,7 @@ export interface TranslationResources {
deleteBackup: string; deleteBackup: string;
export: string; export: string;
exportBackup: string; exportBackup: string;
importBackup: string;
restore: string; restore: string;
restoreBackup: string; restoreBackup: string;
selectTarget: string; selectTarget: string;
@@ -720,6 +721,8 @@ export interface TranslationResources {
empty: string; empty: string;
summary: string; summary: string;
title: string; title: string;
unknownPlatform: string;
unknownTime: string;
}; };
manual: { manual: {
configureWebdav: string; configureWebdav: string;
@@ -737,6 +740,8 @@ export interface TranslationResources {
localBackupExported: string; localBackupExported: string;
localBackupExportFailed: string; localBackupExportFailed: string;
localBackupFailed: string; localBackupFailed: string;
localBackupImported: string;
localBackupImportFailed: string;
passwordRequired: string; passwordRequired: string;
restoreSuccess: string; restoreSuccess: string;
usernameRequired: string; usernameRequired: string;