feat: 优化webdav备份文件恢复管理功能 (#4699)
* feat: 优化webdav备份文件恢复管理功能 * fix: 恢复和删除操作更改为文字而非图标 * feat: 统一坚果云与webdav备份恢复功能
This commit is contained in:
parent
88cbb27557
commit
a54360cc69
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -31,5 +31,13 @@
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/renderer/src/i18n"]
|
||||
"i18n-ally.localesPaths": ["src/renderer/src/i18n/locales"],
|
||||
"i18n-ally.enabledFrameworks": ["react-i18next", "i18next"],
|
||||
"i18n-ally.keystyle": "nested", // 翻译路径格式
|
||||
"i18n-ally.sortKeys": true, // 排序
|
||||
"i18n-ally.namespace": true, // 开启命名空间
|
||||
"i18n-ally.enabledParsers": ["ts", "js", "json"], // 解析语言
|
||||
"i18n-ally.sourceLanguage": "en-us", // 翻译源语言
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.fullReloadOnChanged": true // 界面显示语言
|
||||
}
|
||||
|
||||
@ -120,6 +120,7 @@ export enum IpcChannel {
|
||||
Backup_ListWebdavFiles = 'backup:listWebdavFiles',
|
||||
Backup_CheckConnection = 'backup:checkConnection',
|
||||
Backup_CreateDirectory = 'backup:createDirectory',
|
||||
Backup_DeleteWebdavFile = 'backup:deleteWebdavFile',
|
||||
|
||||
// zip
|
||||
Zip_Compress = 'zip:compress',
|
||||
|
||||
@ -182,6 +182,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle(IpcChannel.Backup_ListWebdavFiles, backupManager.listWebdavFiles)
|
||||
ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection)
|
||||
ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory)
|
||||
ipcMain.handle(IpcChannel.Backup_DeleteWebdavFile, backupManager.deleteWebdavFile)
|
||||
|
||||
// file
|
||||
ipcMain.handle(IpcChannel.File_Open, fileManager.open)
|
||||
|
||||
@ -22,6 +22,7 @@ class BackupManager {
|
||||
this.backupToWebdav = this.backupToWebdav.bind(this)
|
||||
this.restoreFromWebdav = this.restoreFromWebdav.bind(this)
|
||||
this.listWebdavFiles = this.listWebdavFiles.bind(this)
|
||||
this.deleteWebdavFile = this.deleteWebdavFile.bind(this)
|
||||
}
|
||||
|
||||
private async setWritableRecursive(dirPath: string): Promise<void> {
|
||||
@ -309,6 +310,16 @@ class BackupManager {
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
return await webdavClient.createDirectory(path, options)
|
||||
}
|
||||
|
||||
async deleteWebdavFile(_: Electron.IpcMainInvokeEvent, fileName: string, webdavConfig: WebDavConfig) {
|
||||
try {
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
return await webdavClient.deleteFile(fileName)
|
||||
} catch (error: any) {
|
||||
Logger.error('Failed to delete WebDAV file:', error)
|
||||
throw new Error(error.message || 'Failed to delete backup file')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BackupManager
|
||||
|
||||
@ -26,6 +26,7 @@ export default class WebDav {
|
||||
this.putFileContents = this.putFileContents.bind(this)
|
||||
this.getFileContents = this.getFileContents.bind(this)
|
||||
this.createDirectory = this.createDirectory.bind(this)
|
||||
this.deleteFile = this.deleteFile.bind(this)
|
||||
}
|
||||
|
||||
public putFileContents = async (
|
||||
@ -98,4 +99,19 @@ export default class WebDav {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public deleteFile = async (filename: string) => {
|
||||
if (!this.instance) {
|
||||
throw new Error('WebDAV client not initialized')
|
||||
}
|
||||
|
||||
const remoteFilePath = `${this.webdavPath}/${filename}`
|
||||
|
||||
try {
|
||||
return await this.instance.deleteFile(remoteFilePath)
|
||||
} catch (error) {
|
||||
Logger.error('[WebDAV] Error deleting file on WebDAV:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/preload/index.d.ts
vendored
1
src/preload/index.d.ts
vendored
@ -46,6 +46,7 @@ declare global {
|
||||
listWebdavFiles: (webdavConfig: WebDavConfig) => Promise<BackupFile[]>
|
||||
checkConnection: (webdavConfig: WebDavConfig) => Promise<boolean>
|
||||
createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => Promise<void>
|
||||
deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) => Promise<boolean>
|
||||
}
|
||||
file: {
|
||||
select: (options?: OpenDialogOptions) => Promise<FileType[] | null>
|
||||
|
||||
@ -41,7 +41,9 @@ const api = {
|
||||
checkConnection: (webdavConfig: WebDavConfig) =>
|
||||
ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig),
|
||||
createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) =>
|
||||
ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options)
|
||||
ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options),
|
||||
deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) =>
|
||||
ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig)
|
||||
},
|
||||
file: {
|
||||
select: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Select, options),
|
||||
|
||||
283
src/renderer/src/components/WebdavBackupManager.tsx
Normal file
283
src/renderer/src/components/WebdavBackupManager.tsx
Normal file
@ -0,0 +1,283 @@
|
||||
import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { restoreFromWebdav } from '@renderer/services/BackupService'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { Button, message, Modal, Table, Tooltip } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface BackupFile {
|
||||
fileName: string
|
||||
modifiedTime: string
|
||||
size: number
|
||||
}
|
||||
|
||||
interface WebdavConfig {
|
||||
webdavHost: string
|
||||
webdavUser: string
|
||||
webdavPass: string
|
||||
webdavPath: string
|
||||
}
|
||||
|
||||
interface WebdavBackupManagerProps {
|
||||
visible: boolean
|
||||
onClose: () => void
|
||||
webdavConfig: {
|
||||
webdavHost?: string
|
||||
webdavUser?: string
|
||||
webdavPass?: string
|
||||
webdavPath?: string
|
||||
}
|
||||
restoreMethod?: (fileName: string) => Promise<void>
|
||||
}
|
||||
|
||||
export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMethod }: WebdavBackupManagerProps) {
|
||||
const { t } = useTranslation()
|
||||
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [restoring, setRestoring] = useState(false)
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 5,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const { webdavHost, webdavUser, webdavPass, webdavPath } = webdavConfig
|
||||
|
||||
const fetchBackupFiles = useCallback(async () => {
|
||||
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) {
|
||||
message.error(t('message.error.invalid.webdav'))
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const files = await window.api.backup.listWebdavFiles({
|
||||
webdavHost,
|
||||
webdavUser,
|
||||
webdavPass,
|
||||
webdavPath
|
||||
} as WebdavConfig)
|
||||
setBackupFiles(files)
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
total: files.length
|
||||
}))
|
||||
} catch (error: any) {
|
||||
message.error(`${t('settings.data.webdav.backup.manager.fetch.error')}: ${error.message}`)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [webdavHost, webdavUser, webdavPass, webdavPath, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
fetchBackupFiles()
|
||||
setSelectedRowKeys([])
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: 1
|
||||
}))
|
||||
}
|
||||
}, [visible, fetchBackupFiles])
|
||||
|
||||
const handleTableChange = (pagination: any) => {
|
||||
setPagination(pagination)
|
||||
}
|
||||
|
||||
const handleDeleteSelected = async () => {
|
||||
if (selectedRowKeys.length === 0) {
|
||||
message.warning(t('settings.data.webdav.backup.manager.select.files.delete'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) {
|
||||
message.error(t('message.error.invalid.webdav'))
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('settings.data.webdav.backup.manager.delete.confirm.title'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.data.webdav.backup.manager.delete.confirm.multiple', { count: selectedRowKeys.length }),
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
onOk: async () => {
|
||||
setDeleting(true)
|
||||
try {
|
||||
// 依次删除选中的文件
|
||||
for (const key of selectedRowKeys) {
|
||||
await window.api.backup.deleteWebdavFile(key.toString(), {
|
||||
webdavHost,
|
||||
webdavUser,
|
||||
webdavPass,
|
||||
webdavPath
|
||||
} as WebdavConfig)
|
||||
}
|
||||
message.success(
|
||||
t('settings.data.webdav.backup.manager.delete.success.multiple', { count: selectedRowKeys.length })
|
||||
)
|
||||
setSelectedRowKeys([])
|
||||
await fetchBackupFiles()
|
||||
} catch (error: any) {
|
||||
message.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeleteSingle = async (fileName: string) => {
|
||||
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) {
|
||||
message.error(t('message.error.invalid.webdav'))
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('settings.data.webdav.backup.manager.delete.confirm.title'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.data.webdav.backup.manager.delete.confirm.single', { fileName }),
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
onOk: async () => {
|
||||
setDeleting(true)
|
||||
try {
|
||||
await window.api.backup.deleteWebdavFile(fileName, {
|
||||
webdavHost,
|
||||
webdavUser,
|
||||
webdavPass,
|
||||
webdavPath
|
||||
} as WebdavConfig)
|
||||
message.success(t('settings.data.webdav.backup.manager.delete.success.single'))
|
||||
await fetchBackupFiles()
|
||||
} catch (error: any) {
|
||||
message.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleRestore = async (fileName: string) => {
|
||||
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) {
|
||||
message.error(t('message.error.invalid.webdav'))
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('settings.data.webdav.restore.confirm.title'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.data.webdav.restore.confirm.content'),
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
onOk: async () => {
|
||||
setRestoring(true)
|
||||
try {
|
||||
await (restoreMethod || restoreFromWebdav)(fileName)
|
||||
message.success(t('settings.data.webdav.backup.manager.restore.success'))
|
||||
onClose() // 关闭模态框
|
||||
} catch (error: any) {
|
||||
message.error(`${t('settings.data.webdav.backup.manager.restore.error')}: ${error.message}`)
|
||||
} finally {
|
||||
setRestoring(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('settings.data.webdav.backup.manager.columns.fileName'),
|
||||
dataIndex: 'fileName',
|
||||
key: 'fileName',
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
render: (fileName: string) => (
|
||||
<Tooltip placement="topLeft" title={fileName}>
|
||||
{fileName}
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('settings.data.webdav.backup.manager.columns.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
key: 'modifiedTime',
|
||||
width: 180,
|
||||
render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: t('settings.data.webdav.backup.manager.columns.size'),
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
width: 120,
|
||||
render: (size: number) => formatFileSize(size)
|
||||
},
|
||||
{
|
||||
title: t('settings.data.webdav.backup.manager.columns.actions'),
|
||||
key: 'action',
|
||||
width: 160,
|
||||
render: (_: any, record: BackupFile) => (
|
||||
<>
|
||||
<Button type="link" onClick={() => handleRestore(record.fileName)} disabled={restoring || deleting}>
|
||||
{t('settings.data.webdav.backup.manager.restore.text')}
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
onClick={() => handleDeleteSingle(record.fileName)}
|
||||
disabled={deleting || restoring}>
|
||||
{t('settings.data.webdav.backup.manager.delete.text')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: (selectedRowKeys: React.Key[]) => {
|
||||
setSelectedRowKeys(selectedRowKeys)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('settings.data.webdav.backup.manager.title')}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
width={800}
|
||||
footer={[
|
||||
<Button key="refresh" icon={<ReloadOutlined />} onClick={fetchBackupFiles} disabled={loading}>
|
||||
{t('settings.data.webdav.backup.manager.refresh')}
|
||||
</Button>,
|
||||
<Button
|
||||
key="delete"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={handleDeleteSelected}
|
||||
disabled={selectedRowKeys.length === 0 || deleting}
|
||||
loading={deleting}>
|
||||
{t('settings.data.webdav.backup.manager.delete.selected')} ({selectedRowKeys.length})
|
||||
</Button>,
|
||||
<Button key="close" onClick={onClose}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
]}>
|
||||
<Table
|
||||
rowKey="fileName"
|
||||
columns={columns}
|
||||
dataSource={backupFiles}
|
||||
rowSelection={rowSelection}
|
||||
pagination={pagination}
|
||||
loading={loading}
|
||||
onChange={handleTableChange}
|
||||
size="middle"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -879,6 +879,25 @@
|
||||
"backup.button": "Backup to WebDAV",
|
||||
"backup.modal.filename.placeholder": "Please enter backup filename",
|
||||
"backup.modal.title": "Backup to WebDAV",
|
||||
"backup.manager.title": "Backup Data Management",
|
||||
"backup.manager.refresh": "Refresh",
|
||||
"backup.manager.delete.selected": "Delete Selected",
|
||||
"backup.manager.delete.text": "Delete",
|
||||
"backup.manager.restore.text": "Restore",
|
||||
"backup.manager.restore.success": "Restore successful, application will refresh shortly",
|
||||
"backup.manager.restore.error": "Restore failed",
|
||||
"backup.manager.delete.confirm.title": "Confirm Delete",
|
||||
"backup.manager.delete.confirm.single": "Are you sure you want to delete backup file \"{{fileName}}\"? This action cannot be undone.",
|
||||
"backup.manager.delete.confirm.multiple": "Are you sure you want to delete {{count}} selected backup files? This action cannot be undone.",
|
||||
"backup.manager.delete.success.single": "Deleted successfully",
|
||||
"backup.manager.delete.success.multiple": "Successfully deleted {{count}} backup files",
|
||||
"backup.manager.delete.error": "Delete failed",
|
||||
"backup.manager.fetch.error": "Failed to get backup files",
|
||||
"backup.manager.select.files.delete": "Please select backup files to delete",
|
||||
"backup.manager.columns.fileName": "Filename",
|
||||
"backup.manager.columns.modifiedTime": "Modified Time",
|
||||
"backup.manager.columns.size": "Size",
|
||||
"backup.manager.columns.actions": "Actions",
|
||||
"host": "WebDAV Host",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} hour",
|
||||
|
||||
@ -879,6 +879,25 @@
|
||||
"backup.button": "WebDAVにバックアップ",
|
||||
"backup.modal.filename.placeholder": "バックアップファイル名を入力してください",
|
||||
"backup.modal.title": "WebDAV にバックアップ",
|
||||
"backup.manager.title": "バックアップデータ管理",
|
||||
"backup.manager.refresh": "更新",
|
||||
"backup.manager.delete.selected": "選択したものを ",
|
||||
"backup.manager.delete.text": "削除",
|
||||
"backup.manager.restore.text": "復元",
|
||||
"backup.manager.restore.success": "復元が成功しました、アプリケーションは間もなく更新されます",
|
||||
"backup.manager.restore.error": "復元に失敗しました",
|
||||
"backup.manager.delete.confirm.title": "削除の確認",
|
||||
"backup.manager.delete.confirm.single": "バックアップファイル \"{{fileName}}\" を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"backup.manager.delete.confirm.multiple": "選択した {{count}} 個のバックアップファイルを削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"backup.manager.delete.success.single": "削除が成功しました",
|
||||
"backup.manager.delete.success.multiple": "{{count}} 個のバックアップファイルを削除しました",
|
||||
"backup.manager.delete.error": "削除に失敗しました",
|
||||
"backup.manager.fetch.error": "バックアップファイルの取得に失敗しました",
|
||||
"backup.manager.select.files.delete": "削除するバックアップファイルを選択してください",
|
||||
"backup.manager.columns.fileName": "ファイル名",
|
||||
"backup.manager.columns.modifiedTime": "更新日時",
|
||||
"backup.manager.columns.size": "サイズ",
|
||||
"backup.manager.columns.actions": "操作",
|
||||
"host": "WebDAVホスト",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 時間",
|
||||
|
||||
@ -879,6 +879,25 @@
|
||||
"backup.button": "Резервное копирование на WebDAV",
|
||||
"backup.modal.filename.placeholder": "Введите имя файла резервной копии",
|
||||
"backup.modal.title": "Резервное копирование на WebDAV",
|
||||
"backup.manager.title": "Управление резервными копиями",
|
||||
"backup.manager.refresh": "Обновить",
|
||||
"backup.manager.delete.selected": "Удалить выбранные",
|
||||
"backup.manager.delete.text": "Удалить",
|
||||
"backup.manager.restore.text": "Восстановить",
|
||||
"backup.manager.restore.success": "Восстановление прошло успешно, приложение скоро обновится",
|
||||
"backup.manager.restore.error": "Ошибка восстановления",
|
||||
"backup.manager.delete.confirm.title": "Подтверждение удаления",
|
||||
"backup.manager.delete.confirm.single": "Вы уверены, что хотите удалить резервную копию \"{{fileName}}\"? Это действие нельзя отменить.",
|
||||
"backup.manager.delete.confirm.multiple": "Вы уверены, что хотите удалить {{count}} выбранных резервных копий? Это действие нельзя отменить.",
|
||||
"backup.manager.delete.success.single": "Успешно удалено",
|
||||
"backup.manager.delete.success.multiple": "Успешно удалено {{count}} резервных копий",
|
||||
"backup.manager.delete.error": "Ошибка удаления",
|
||||
"backup.manager.fetch.error": "Ошибка получения файлов резервных копий",
|
||||
"backup.manager.select.files.delete": "Выберите файлы резервных копий для удаления",
|
||||
"backup.manager.columns.fileName": "Имя файла",
|
||||
"backup.manager.columns.modifiedTime": "Время изменения",
|
||||
"backup.manager.columns.size": "Размер",
|
||||
"backup.manager.columns.actions": "Действия",
|
||||
"host": "Хост WebDAV",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} час",
|
||||
|
||||
@ -881,6 +881,25 @@
|
||||
"backup.button": "备份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "请输入备份文件名",
|
||||
"backup.modal.title": "备份到 WebDAV",
|
||||
"backup.manager.title": "备份数据管理",
|
||||
"backup.manager.refresh": "刷新",
|
||||
"backup.manager.delete.selected": "删除选中",
|
||||
"backup.manager.delete.text": "删除",
|
||||
"backup.manager.restore.text": "恢复",
|
||||
"backup.manager.restore.success": "恢复成功,应用将在几秒后刷新",
|
||||
"backup.manager.restore.error": "恢复失败",
|
||||
"backup.manager.delete.confirm.title": "确认删除",
|
||||
"backup.manager.delete.confirm.single": "确定要删除备份文件 \"{{fileName}}\" 吗?此操作不可恢复。",
|
||||
"backup.manager.delete.confirm.multiple": "确定要删除选中的 {{count}} 个备份文件吗?此操作不可恢复。",
|
||||
"backup.manager.delete.success.single": "删除成功",
|
||||
"backup.manager.delete.success.multiple": "成功删除 {{count}} 个备份文件",
|
||||
"backup.manager.delete.error": "删除失败",
|
||||
"backup.manager.fetch.error": "获取备份文件失败",
|
||||
"backup.manager.select.files.delete": "请选择要删除的备份文件",
|
||||
"backup.manager.columns.fileName": "文件名",
|
||||
"backup.manager.columns.modifiedTime": "修改时间",
|
||||
"backup.manager.columns.size": "大小",
|
||||
"backup.manager.columns.actions": "操作",
|
||||
"host": "WebDAV 地址",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 小时",
|
||||
|
||||
@ -879,6 +879,25 @@
|
||||
"backup.button": "備份到 WebDAV",
|
||||
"backup.modal.filename.placeholder": "請輸入備份文件名",
|
||||
"backup.modal.title": "備份到 WebDAV",
|
||||
"backup.manager.title": "備份數據管理",
|
||||
"backup.manager.refresh": "刷新",
|
||||
"backup.manager.delete.selected": "刪除選中",
|
||||
"backup.manager.delete.text": "刪除",
|
||||
"backup.manager.restore.text": "恢復",
|
||||
"backup.manager.restore.success": "恢復成功,應用將在幾秒後刷新",
|
||||
"backup.manager.restore.error": "恢復失敗",
|
||||
"backup.manager.delete.confirm.title": "確認刪除",
|
||||
"backup.manager.delete.confirm.single": "確定要刪除備份文件 \"{{fileName}}\" 嗎?此操作不可恢復。",
|
||||
"backup.manager.delete.confirm.multiple": "確定要刪除選中的 {{count}} 個備份文件嗎?此操作不可恢復。",
|
||||
"backup.manager.delete.success.single": "刪除成功",
|
||||
"backup.manager.delete.success.multiple": "成功刪除 {{count}} 個備份文件",
|
||||
"backup.manager.delete.error": "刪除失敗",
|
||||
"backup.manager.fetch.error": "獲取備份文件失敗",
|
||||
"backup.manager.select.files.delete": "請選擇要刪除的備份文件",
|
||||
"backup.manager.columns.fileName": "文件名",
|
||||
"backup.manager.columns.modifiedTime": "修改時間",
|
||||
"backup.manager.columns.size": "大小",
|
||||
"backup.manager.columns.actions": "操作",
|
||||
"host": "WebDAV 主機位址",
|
||||
"host.placeholder": "http://localhost:8080",
|
||||
"hour_interval_one": "{{count}} 小時",
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { CheckOutlined, FolderOutlined, LoadingOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import NutstorePathPopup from '@renderer/components/Popups/NutsorePathPopup'
|
||||
import {
|
||||
useWebdavBackupModal,
|
||||
useWebdavRestoreModal,
|
||||
WebdavBackupModal,
|
||||
WebdavRestoreModal
|
||||
} from '@renderer/components/WebdavModals'
|
||||
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
|
||||
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useNutstoreSSO } from '@renderer/hooks/useNutstoreSSO'
|
||||
import {
|
||||
@ -54,6 +50,8 @@ const NutstoreSettings: FC = () => {
|
||||
|
||||
const nutstoreSSOHandler = useNutstoreSSO()
|
||||
|
||||
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
|
||||
|
||||
const handleClickNutstoreSSO = useCallback(async () => {
|
||||
const ssoUrl = await window.api.nutstore.getSSOUrl()
|
||||
window.open(ssoUrl, '_blank')
|
||||
@ -118,24 +116,6 @@ const NutstoreSettings: FC = () => {
|
||||
backupMethod: backupToNutstore
|
||||
})
|
||||
|
||||
const {
|
||||
isRestoreModalVisible,
|
||||
handleRestore,
|
||||
handleCancel: handleCancelRestore,
|
||||
restoring,
|
||||
selectedFile,
|
||||
setSelectedFile,
|
||||
loadingFiles,
|
||||
backupFiles,
|
||||
showRestoreModal
|
||||
} = useWebdavRestoreModal({
|
||||
restoreMethod: restoreFromNutstore,
|
||||
webdavHost: NUTSTORE_HOST,
|
||||
webdavUser: nutstoreUsername,
|
||||
webdavPass: nutstorePass,
|
||||
webdavPath: storagePath
|
||||
})
|
||||
|
||||
const onSyncIntervalChange = (value: number) => {
|
||||
setSyncInterval(value)
|
||||
dispatch(setNutstoreSyncInterval(value))
|
||||
@ -205,6 +185,14 @@ const NutstoreSettings: FC = () => {
|
||||
|
||||
const isLogin = nutstoreToken && nutstoreUsername
|
||||
|
||||
const showBackupManager = () => {
|
||||
setBackupManagerVisible(true)
|
||||
}
|
||||
|
||||
const closeBackupManager = () => {
|
||||
setBackupManagerVisible(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>{t('settings.data.nutstore.title')}</SettingTitle>
|
||||
@ -269,7 +257,7 @@ const NutstoreSettings: FC = () => {
|
||||
<Button onClick={showBackupModal} loading={backuping}>
|
||||
{t('settings.data.nutstore.backup.button')}
|
||||
</Button>
|
||||
<Button onClick={showRestoreModal} loading={restoring}>
|
||||
<Button onClick={showBackupManager} disabled={!nutstoreToken}>
|
||||
{t('settings.data.nutstore.restore.button')}
|
||||
</Button>
|
||||
</HStack>
|
||||
@ -311,15 +299,16 @@ const NutstoreSettings: FC = () => {
|
||||
setCustomFileName={setCustomFileName}
|
||||
/>
|
||||
|
||||
<WebdavRestoreModal
|
||||
isRestoreModalVisible={isRestoreModalVisible}
|
||||
handleRestore={handleRestore}
|
||||
handleCancel={handleCancelRestore}
|
||||
restoring={restoring}
|
||||
selectedFile={selectedFile}
|
||||
setSelectedFile={setSelectedFile}
|
||||
loadingFiles={loadingFiles}
|
||||
backupFiles={backupFiles}
|
||||
<WebdavBackupManager
|
||||
visible={backupManagerVisible}
|
||||
onClose={closeBackupManager}
|
||||
webdavConfig={{
|
||||
webdavHost: NUTSTORE_HOST,
|
||||
webdavUser: nutstoreUsername,
|
||||
webdavPass: nutstorePass,
|
||||
webdavPath: storagePath
|
||||
}}
|
||||
restoreMethod={restoreFromNutstore}
|
||||
/>
|
||||
</>
|
||||
</SettingGroup>
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import {
|
||||
useWebdavBackupModal,
|
||||
useWebdavRestoreModal,
|
||||
WebdavBackupModal,
|
||||
WebdavRestoreModal
|
||||
} from '@renderer/components/WebdavModals'
|
||||
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
|
||||
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
||||
@ -38,6 +34,7 @@ const WebDavSettings: FC = () => {
|
||||
const [webdavUser, setWebdavUser] = useState<string | undefined>(webDAVUser)
|
||||
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
|
||||
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
|
||||
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
|
||||
|
||||
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
|
||||
|
||||
@ -89,17 +86,13 @@ const WebDavSettings: FC = () => {
|
||||
const { isModalVisible, handleBackup, handleCancel, backuping, customFileName, setCustomFileName, showBackupModal } =
|
||||
useWebdavBackupModal()
|
||||
|
||||
const {
|
||||
isRestoreModalVisible,
|
||||
handleRestore,
|
||||
handleCancel: handleCancelRestore,
|
||||
restoring,
|
||||
selectedFile,
|
||||
setSelectedFile,
|
||||
loadingFiles,
|
||||
backupFiles,
|
||||
showRestoreModal
|
||||
} = useWebdavRestoreModal({ webdavHost, webdavUser, webdavPass, webdavPath })
|
||||
const showBackupManager = () => {
|
||||
setBackupManagerVisible(true)
|
||||
}
|
||||
|
||||
const closeBackupManager = () => {
|
||||
setBackupManagerVisible(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup theme={theme}>
|
||||
@ -156,7 +149,10 @@ const WebDavSettings: FC = () => {
|
||||
<Button onClick={showBackupModal} icon={<SaveOutlined />} loading={backuping}>
|
||||
{t('settings.data.webdav.backup.button')}
|
||||
</Button>
|
||||
<Button onClick={showRestoreModal} icon={<FolderOpenOutlined />} loading={restoring}>
|
||||
<Button
|
||||
onClick={showBackupManager}
|
||||
icon={<FolderOpenOutlined />}
|
||||
disabled={!webdavHost || !webdavUser || !webdavPass || !webdavPath}>
|
||||
{t('settings.data.webdav.restore.button')}
|
||||
</Button>
|
||||
</HStack>
|
||||
@ -196,15 +192,15 @@ const WebDavSettings: FC = () => {
|
||||
setCustomFileName={setCustomFileName}
|
||||
/>
|
||||
|
||||
<WebdavRestoreModal
|
||||
isRestoreModalVisible={isRestoreModalVisible}
|
||||
handleRestore={handleRestore}
|
||||
handleCancel={handleCancelRestore}
|
||||
restoring={restoring}
|
||||
selectedFile={selectedFile}
|
||||
setSelectedFile={setSelectedFile}
|
||||
loadingFiles={loadingFiles}
|
||||
backupFiles={backupFiles}
|
||||
<WebdavBackupManager
|
||||
visible={backupManagerVisible}
|
||||
onClose={closeBackupManager}
|
||||
webdavConfig={{
|
||||
webdavHost,
|
||||
webdavUser,
|
||||
webdavPass,
|
||||
webdavPath
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</SettingGroup>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user