feat(restoreFromWebdav): make credentials and path optional (#6922)

* feat(BackupService): add feedback messages for backup operations

* feat(WebDAV): Allow optional username, password, and path for unauthenticated access
This commit is contained in:
Doekin 2025-06-07 21:25:08 +08:00 committed by GitHub
parent ea7766db44
commit c38a6cdfbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 22 additions and 22 deletions

View File

@ -1,7 +1,8 @@
import { WebDavConfig } from '@types' import { WebDavConfig } from '@types'
import Logger from 'electron-log' import Logger from 'electron-log'
import Stream from 'stream'
import https from 'https' import https from 'https'
import path from 'path'
import Stream from 'stream'
import { import {
BufferLike, BufferLike,
createClient, createClient,
@ -15,7 +16,7 @@ export default class WebDav {
private webdavPath: string private webdavPath: string
constructor(params: WebDavConfig) { constructor(params: WebDavConfig) {
this.webdavPath = params.webdavPath this.webdavPath = params.webdavPath || '/'
this.instance = createClient(params.webdavHost, { this.instance = createClient(params.webdavHost, {
username: params.webdavUser, username: params.webdavUser,
@ -51,7 +52,7 @@ export default class WebDav {
throw error throw error
} }
const remoteFilePath = `${this.webdavPath}/${filename}` const remoteFilePath = path.posix.join(this.webdavPath, filename)
try { try {
return await this.instance.putFileContents(remoteFilePath, data, options) return await this.instance.putFileContents(remoteFilePath, data, options)
@ -66,7 +67,7 @@ export default class WebDav {
throw new Error('WebDAV client not initialized') throw new Error('WebDAV client not initialized')
} }
const remoteFilePath = `${this.webdavPath}/${filename}` const remoteFilePath = path.posix.join(this.webdavPath, filename)
try { try {
return await this.instance.getFileContents(remoteFilePath, options) return await this.instance.getFileContents(remoteFilePath, options)
@ -120,7 +121,7 @@ export default class WebDav {
throw new Error('WebDAV client not initialized') throw new Error('WebDAV client not initialized')
} }
const remoteFilePath = `${this.webdavPath}/${filename}` const remoteFilePath = path.posix.join(this.webdavPath, filename)
try { try {
return await this.instance.deleteFile(remoteFilePath) return await this.instance.deleteFile(remoteFilePath)

View File

@ -14,9 +14,9 @@ interface BackupFile {
interface WebdavConfig { interface WebdavConfig {
webdavHost: string webdavHost: string
webdavUser: string webdavUser?: string
webdavPass: string webdavPass?: string
webdavPath: string webdavPath?: string
} }
interface WebdavBackupManagerProps { interface WebdavBackupManagerProps {
@ -47,7 +47,7 @@ export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMet
const { webdavHost, webdavUser, webdavPass, webdavPath } = webdavConfig const { webdavHost, webdavUser, webdavPass, webdavPath } = webdavConfig
const fetchBackupFiles = useCallback(async () => { const fetchBackupFiles = useCallback(async () => {
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!webdavHost) {
message.error(t('message.error.invalid.webdav')) message.error(t('message.error.invalid.webdav'))
return return
} }
@ -93,7 +93,7 @@ export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMet
return return
} }
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!webdavHost) {
message.error(t('message.error.invalid.webdav')) message.error(t('message.error.invalid.webdav'))
return return
} }
@ -132,7 +132,7 @@ export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMet
} }
const handleDeleteSingle = async (fileName: string) => { const handleDeleteSingle = async (fileName: string) => {
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!webdavHost) {
message.error(t('message.error.invalid.webdav')) message.error(t('message.error.invalid.webdav'))
return return
} }
@ -165,7 +165,7 @@ export function WebdavBackupManager({ visible, onClose, webdavConfig, restoreMet
} }
const handleRestore = async (fileName: string) => { const handleRestore = async (fileName: string) => {
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!webdavHost) {
message.error(t('message.error.invalid.webdav')) message.error(t('message.error.invalid.webdav'))
return return
} }

View File

@ -123,7 +123,7 @@ export function useWebdavRestoreModal({
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([]) const [backupFiles, setBackupFiles] = useState<BackupFile[]>([])
const showRestoreModal = useCallback(async () => { const showRestoreModal = useCallback(async () => {
if (!webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!webdavHost) {
window.message.error({ content: t('message.error.invalid.webdav'), key: 'webdav-error' }) window.message.error({ content: t('message.error.invalid.webdav'), key: 'webdav-error' })
return return
} }
@ -146,7 +146,7 @@ export function useWebdavRestoreModal({
}, [webdavHost, webdavUser, webdavPass, webdavPath, t]) }, [webdavHost, webdavUser, webdavPass, webdavPath, t])
const handleRestore = useCallback(async () => { const handleRestore = useCallback(async () => {
if (!selectedFile || !webdavHost || !webdavUser || !webdavPass || !webdavPath) { if (!selectedFile || !webdavHost) {
window.message.error({ window.message.error({
content: !selectedFile ? t('message.error.no.file.selected') : t('message.error.invalid.webdav'), content: !selectedFile ? t('message.error.no.file.selected') : t('message.error.invalid.webdav'),
key: 'restore-error' key: 'restore-error'
@ -170,7 +170,7 @@ export function useWebdavRestoreModal({
} }
} }
}) })
}, [selectedFile, webdavHost, webdavUser, webdavPass, webdavPath, t, restoreMethod]) }, [selectedFile, webdavHost, t, restoreMethod])
const handleCancel = () => { const handleCancel = () => {
setIsRestoreModalVisible(false) setIsRestoreModalVisible(false)

View File

@ -165,10 +165,7 @@ 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 <Button onClick={showBackupManager} icon={<FolderOpenOutlined />} disabled={!webdavHost}>
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>

View File

@ -137,6 +137,7 @@ export async function backupToWebdav({
timestamp: Date.now(), timestamp: Date.now(),
source: 'backup' source: 'backup'
}) })
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
// 清理旧备份文件 // 清理旧备份文件
if (webdavMaxBackups > 0) { if (webdavMaxBackups > 0) {
@ -202,6 +203,7 @@ export async function backupToWebdav({
source: 'backup' source: 'backup'
}) })
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message })) store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
console.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error) console.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error)
throw error throw error
} finally { } finally {

View File

@ -347,9 +347,9 @@ export type CodeStyleVarious = 'auto' | string
export type WebDavConfig = { export type WebDavConfig = {
webdavHost: string webdavHost: string
webdavUser: string webdavUser?: string
webdavPass: string webdavPass?: string
webdavPath: string webdavPath?: string
fileName?: string fileName?: string
skipBackupFile?: boolean skipBackupFile?: boolean
} }