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]": {
|
"[markdown]": {
|
||||||
"files.trimTrailingWhitespace": false
|
"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_ListWebdavFiles = 'backup:listWebdavFiles',
|
||||||
Backup_CheckConnection = 'backup:checkConnection',
|
Backup_CheckConnection = 'backup:checkConnection',
|
||||||
Backup_CreateDirectory = 'backup:createDirectory',
|
Backup_CreateDirectory = 'backup:createDirectory',
|
||||||
|
Backup_DeleteWebdavFile = 'backup:deleteWebdavFile',
|
||||||
|
|
||||||
// zip
|
// zip
|
||||||
Zip_Compress = 'zip:compress',
|
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_ListWebdavFiles, backupManager.listWebdavFiles)
|
||||||
ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection)
|
ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection)
|
||||||
ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory)
|
ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory)
|
||||||
|
ipcMain.handle(IpcChannel.Backup_DeleteWebdavFile, backupManager.deleteWebdavFile)
|
||||||
|
|
||||||
// file
|
// file
|
||||||
ipcMain.handle(IpcChannel.File_Open, fileManager.open)
|
ipcMain.handle(IpcChannel.File_Open, fileManager.open)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ class BackupManager {
|
|||||||
this.backupToWebdav = this.backupToWebdav.bind(this)
|
this.backupToWebdav = this.backupToWebdav.bind(this)
|
||||||
this.restoreFromWebdav = this.restoreFromWebdav.bind(this)
|
this.restoreFromWebdav = this.restoreFromWebdav.bind(this)
|
||||||
this.listWebdavFiles = this.listWebdavFiles.bind(this)
|
this.listWebdavFiles = this.listWebdavFiles.bind(this)
|
||||||
|
this.deleteWebdavFile = this.deleteWebdavFile.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setWritableRecursive(dirPath: string): Promise<void> {
|
private async setWritableRecursive(dirPath: string): Promise<void> {
|
||||||
@ -309,6 +310,16 @@ class BackupManager {
|
|||||||
const webdavClient = new WebDav(webdavConfig)
|
const webdavClient = new WebDav(webdavConfig)
|
||||||
return await webdavClient.createDirectory(path, options)
|
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
|
export default BackupManager
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export default class WebDav {
|
|||||||
this.putFileContents = this.putFileContents.bind(this)
|
this.putFileContents = this.putFileContents.bind(this)
|
||||||
this.getFileContents = this.getFileContents.bind(this)
|
this.getFileContents = this.getFileContents.bind(this)
|
||||||
this.createDirectory = this.createDirectory.bind(this)
|
this.createDirectory = this.createDirectory.bind(this)
|
||||||
|
this.deleteFile = this.deleteFile.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
public putFileContents = async (
|
public putFileContents = async (
|
||||||
@ -98,4 +99,19 @@ export default class WebDav {
|
|||||||
throw error
|
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[]>
|
listWebdavFiles: (webdavConfig: WebDavConfig) => Promise<BackupFile[]>
|
||||||
checkConnection: (webdavConfig: WebDavConfig) => Promise<boolean>
|
checkConnection: (webdavConfig: WebDavConfig) => Promise<boolean>
|
||||||
createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => Promise<void>
|
createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => Promise<void>
|
||||||
|
deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) => Promise<boolean>
|
||||||
}
|
}
|
||||||
file: {
|
file: {
|
||||||
select: (options?: OpenDialogOptions) => Promise<FileType[] | null>
|
select: (options?: OpenDialogOptions) => Promise<FileType[] | null>
|
||||||
|
|||||||
@ -41,7 +41,9 @@ const api = {
|
|||||||
checkConnection: (webdavConfig: WebDavConfig) =>
|
checkConnection: (webdavConfig: WebDavConfig) =>
|
||||||
ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig),
|
ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig),
|
||||||
createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) =>
|
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: {
|
file: {
|
||||||
select: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Select, options),
|
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.button": "Backup to WebDAV",
|
||||||
"backup.modal.filename.placeholder": "Please enter backup filename",
|
"backup.modal.filename.placeholder": "Please enter backup filename",
|
||||||
"backup.modal.title": "Backup to WebDAV",
|
"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": "WebDAV Host",
|
||||||
"host.placeholder": "http://localhost:8080",
|
"host.placeholder": "http://localhost:8080",
|
||||||
"hour_interval_one": "{{count}} hour",
|
"hour_interval_one": "{{count}} hour",
|
||||||
|
|||||||
@ -879,6 +879,25 @@
|
|||||||
"backup.button": "WebDAVにバックアップ",
|
"backup.button": "WebDAVにバックアップ",
|
||||||
"backup.modal.filename.placeholder": "バックアップファイル名を入力してください",
|
"backup.modal.filename.placeholder": "バックアップファイル名を入力してください",
|
||||||
"backup.modal.title": "WebDAV にバックアップ",
|
"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": "WebDAVホスト",
|
||||||
"host.placeholder": "http://localhost:8080",
|
"host.placeholder": "http://localhost:8080",
|
||||||
"hour_interval_one": "{{count}} 時間",
|
"hour_interval_one": "{{count}} 時間",
|
||||||
|
|||||||
@ -879,6 +879,25 @@
|
|||||||
"backup.button": "Резервное копирование на WebDAV",
|
"backup.button": "Резервное копирование на WebDAV",
|
||||||
"backup.modal.filename.placeholder": "Введите имя файла резервной копии",
|
"backup.modal.filename.placeholder": "Введите имя файла резервной копии",
|
||||||
"backup.modal.title": "Резервное копирование на WebDAV",
|
"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": "Хост WebDAV",
|
||||||
"host.placeholder": "http://localhost:8080",
|
"host.placeholder": "http://localhost:8080",
|
||||||
"hour_interval_one": "{{count}} час",
|
"hour_interval_one": "{{count}} час",
|
||||||
|
|||||||
@ -881,6 +881,25 @@
|
|||||||
"backup.button": "备份到 WebDAV",
|
"backup.button": "备份到 WebDAV",
|
||||||
"backup.modal.filename.placeholder": "请输入备份文件名",
|
"backup.modal.filename.placeholder": "请输入备份文件名",
|
||||||
"backup.modal.title": "备份到 WebDAV",
|
"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": "WebDAV 地址",
|
||||||
"host.placeholder": "http://localhost:8080",
|
"host.placeholder": "http://localhost:8080",
|
||||||
"hour_interval_one": "{{count}} 小时",
|
"hour_interval_one": "{{count}} 小时",
|
||||||
|
|||||||
@ -879,6 +879,25 @@
|
|||||||
"backup.button": "備份到 WebDAV",
|
"backup.button": "備份到 WebDAV",
|
||||||
"backup.modal.filename.placeholder": "請輸入備份文件名",
|
"backup.modal.filename.placeholder": "請輸入備份文件名",
|
||||||
"backup.modal.title": "備份到 WebDAV",
|
"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": "WebDAV 主機位址",
|
||||||
"host.placeholder": "http://localhost:8080",
|
"host.placeholder": "http://localhost:8080",
|
||||||
"hour_interval_one": "{{count}} 小時",
|
"hour_interval_one": "{{count}} 小時",
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import { CheckOutlined, FolderOutlined, LoadingOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
import { CheckOutlined, FolderOutlined, LoadingOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import NutstorePathPopup from '@renderer/components/Popups/NutsorePathPopup'
|
import NutstorePathPopup from '@renderer/components/Popups/NutsorePathPopup'
|
||||||
import {
|
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
|
||||||
useWebdavBackupModal,
|
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
|
||||||
useWebdavRestoreModal,
|
|
||||||
WebdavBackupModal,
|
|
||||||
WebdavRestoreModal
|
|
||||||
} from '@renderer/components/WebdavModals'
|
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useNutstoreSSO } from '@renderer/hooks/useNutstoreSSO'
|
import { useNutstoreSSO } from '@renderer/hooks/useNutstoreSSO'
|
||||||
import {
|
import {
|
||||||
@ -54,6 +50,8 @@ const NutstoreSettings: FC = () => {
|
|||||||
|
|
||||||
const nutstoreSSOHandler = useNutstoreSSO()
|
const nutstoreSSOHandler = useNutstoreSSO()
|
||||||
|
|
||||||
|
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
|
||||||
|
|
||||||
const handleClickNutstoreSSO = useCallback(async () => {
|
const handleClickNutstoreSSO = useCallback(async () => {
|
||||||
const ssoUrl = await window.api.nutstore.getSSOUrl()
|
const ssoUrl = await window.api.nutstore.getSSOUrl()
|
||||||
window.open(ssoUrl, '_blank')
|
window.open(ssoUrl, '_blank')
|
||||||
@ -118,24 +116,6 @@ const NutstoreSettings: FC = () => {
|
|||||||
backupMethod: backupToNutstore
|
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) => {
|
const onSyncIntervalChange = (value: number) => {
|
||||||
setSyncInterval(value)
|
setSyncInterval(value)
|
||||||
dispatch(setNutstoreSyncInterval(value))
|
dispatch(setNutstoreSyncInterval(value))
|
||||||
@ -205,6 +185,14 @@ const NutstoreSettings: FC = () => {
|
|||||||
|
|
||||||
const isLogin = nutstoreToken && nutstoreUsername
|
const isLogin = nutstoreToken && nutstoreUsername
|
||||||
|
|
||||||
|
const showBackupManager = () => {
|
||||||
|
setBackupManagerVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeBackupManager = () => {
|
||||||
|
setBackupManagerVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
<SettingTitle>{t('settings.data.nutstore.title')}</SettingTitle>
|
<SettingTitle>{t('settings.data.nutstore.title')}</SettingTitle>
|
||||||
@ -269,7 +257,7 @@ const NutstoreSettings: FC = () => {
|
|||||||
<Button onClick={showBackupModal} loading={backuping}>
|
<Button onClick={showBackupModal} loading={backuping}>
|
||||||
{t('settings.data.nutstore.backup.button')}
|
{t('settings.data.nutstore.backup.button')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={showRestoreModal} loading={restoring}>
|
<Button onClick={showBackupManager} disabled={!nutstoreToken}>
|
||||||
{t('settings.data.nutstore.restore.button')}
|
{t('settings.data.nutstore.restore.button')}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
@ -311,15 +299,16 @@ const NutstoreSettings: FC = () => {
|
|||||||
setCustomFileName={setCustomFileName}
|
setCustomFileName={setCustomFileName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WebdavRestoreModal
|
<WebdavBackupManager
|
||||||
isRestoreModalVisible={isRestoreModalVisible}
|
visible={backupManagerVisible}
|
||||||
handleRestore={handleRestore}
|
onClose={closeBackupManager}
|
||||||
handleCancel={handleCancelRestore}
|
webdavConfig={{
|
||||||
restoring={restoring}
|
webdavHost: NUTSTORE_HOST,
|
||||||
selectedFile={selectedFile}
|
webdavUser: nutstoreUsername,
|
||||||
setSelectedFile={setSelectedFile}
|
webdavPass: nutstorePass,
|
||||||
loadingFiles={loadingFiles}
|
webdavPath: storagePath
|
||||||
backupFiles={backupFiles}
|
}}
|
||||||
|
restoreMethod={restoreFromNutstore}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|||||||
@ -1,11 +1,7 @@
|
|||||||
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import {
|
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
|
||||||
useWebdavBackupModal,
|
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
|
||||||
useWebdavRestoreModal,
|
|
||||||
WebdavBackupModal,
|
|
||||||
WebdavRestoreModal
|
|
||||||
} from '@renderer/components/WebdavModals'
|
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
import { startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
|
||||||
@ -38,6 +34,7 @@ const WebDavSettings: FC = () => {
|
|||||||
const [webdavUser, setWebdavUser] = useState<string | undefined>(webDAVUser)
|
const [webdavUser, setWebdavUser] = useState<string | undefined>(webDAVUser)
|
||||||
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
|
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
|
||||||
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
|
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
|
||||||
|
const [backupManagerVisible, setBackupManagerVisible] = useState(false)
|
||||||
|
|
||||||
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
|
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
|
||||||
|
|
||||||
@ -89,17 +86,13 @@ const WebDavSettings: FC = () => {
|
|||||||
const { isModalVisible, handleBackup, handleCancel, backuping, customFileName, setCustomFileName, showBackupModal } =
|
const { isModalVisible, handleBackup, handleCancel, backuping, customFileName, setCustomFileName, showBackupModal } =
|
||||||
useWebdavBackupModal()
|
useWebdavBackupModal()
|
||||||
|
|
||||||
const {
|
const showBackupManager = () => {
|
||||||
isRestoreModalVisible,
|
setBackupManagerVisible(true)
|
||||||
handleRestore,
|
}
|
||||||
handleCancel: handleCancelRestore,
|
|
||||||
restoring,
|
const closeBackupManager = () => {
|
||||||
selectedFile,
|
setBackupManagerVisible(false)
|
||||||
setSelectedFile,
|
}
|
||||||
loadingFiles,
|
|
||||||
backupFiles,
|
|
||||||
showRestoreModal
|
|
||||||
} = useWebdavRestoreModal({ webdavHost, webdavUser, webdavPass, webdavPath })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme}>
|
||||||
@ -156,7 +149,10 @@ const WebDavSettings: FC = () => {
|
|||||||
<Button onClick={showBackupModal} icon={<SaveOutlined />} loading={backuping}>
|
<Button onClick={showBackupModal} icon={<SaveOutlined />} loading={backuping}>
|
||||||
{t('settings.data.webdav.backup.button')}
|
{t('settings.data.webdav.backup.button')}
|
||||||
</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')}
|
{t('settings.data.webdav.restore.button')}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
@ -196,15 +192,15 @@ const WebDavSettings: FC = () => {
|
|||||||
setCustomFileName={setCustomFileName}
|
setCustomFileName={setCustomFileName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WebdavRestoreModal
|
<WebdavBackupManager
|
||||||
isRestoreModalVisible={isRestoreModalVisible}
|
visible={backupManagerVisible}
|
||||||
handleRestore={handleRestore}
|
onClose={closeBackupManager}
|
||||||
handleCancel={handleCancelRestore}
|
webdavConfig={{
|
||||||
restoring={restoring}
|
webdavHost,
|
||||||
selectedFile={selectedFile}
|
webdavUser,
|
||||||
setSelectedFile={setSelectedFile}
|
webdavPass,
|
||||||
loadingFiles={loadingFiles}
|
webdavPath
|
||||||
backupFiles={backupFiles}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user