feat: add sync status show

This commit is contained in:
kangfenmao 2025-01-02 11:07:11 +08:00
parent 63cdc15bc2
commit 1d3a01dd49
8 changed files with 137 additions and 47 deletions

View File

@ -362,7 +362,12 @@
"webdav.minutes": "Minutes", "webdav.minutes": "Minutes",
"webdav.restore.button": "Restore from WebDAV", "webdav.restore.button": "Restore from WebDAV",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "WebDAV User" "webdav.user": "WebDAV User",
"webdav.syncStatus": "Sync Status",
"webdav.autoSync.off": "Off",
"webdav.noSync": "Waiting for next sync",
"webdav.syncError": "Sync Error",
"webdav.lastSync": "Last Sync"
}, },
"display.title": "Display Settings", "display.title": "Display Settings",
"font_size.title": "Message font size", "font_size.title": "Message font size",

View File

@ -360,7 +360,12 @@
"webdav.minutes": "分", "webdav.minutes": "分",
"webdav.restore.button": "WebDAVから復元", "webdav.restore.button": "WebDAVから復元",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "WebDAVユーザー" "webdav.user": "WebDAVユーザー",
"webdav.syncStatus": "同期状態",
"webdav.autoSync.off": "オフ",
"webdav.noSync": "次回の同期を待っています",
"webdav.syncError": "同期エラー",
"webdav.lastSync": "最終同期"
}, },
"display.title": "表示設定", "display.title": "表示設定",
"font_size.title": "メッセージのフォントサイズ", "font_size.title": "メッセージのフォントサイズ",

View File

@ -362,7 +362,12 @@
"webdav.minutes": "минут", "webdav.minutes": "минут",
"webdav.restore.button": "Восстановление с WebDAV", "webdav.restore.button": "Восстановление с WebDAV",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "Пользователь WebDAV" "webdav.user": "Пользователь WebDAV",
"webdav.syncStatus": "Статус синхронизации",
"webdav.autoSync.off": "Выключено",
"webdav.noSync": "Ожидание следующей синхронизации",
"webdav.syncError": "Ошибка синхронизации",
"webdav.lastSync": "Последняя синхронизация"
}, },
"display.title": "Настройки отображения", "display.title": "Настройки отображения",
"font_size.title": "Размер шрифта сообщений", "font_size.title": "Размер шрифта сообщений",

View File

@ -363,7 +363,12 @@
"webdav.minutes": "分钟", "webdav.minutes": "分钟",
"webdav.restore.button": "从 WebDAV 恢复", "webdav.restore.button": "从 WebDAV 恢复",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "WebDAV 用户名" "webdav.user": "WebDAV 用户名",
"webdav.syncStatus": "同步状态",
"webdav.autoSync.off": "关闭",
"webdav.noSync": "等待下次同步",
"webdav.syncError": "同步错误",
"webdav.lastSync": "上次同步时间"
}, },
"display.title": "显示设置", "display.title": "显示设置",
"font_size.title": "消息字体大小", "font_size.title": "消息字体大小",

View File

@ -362,7 +362,12 @@
"webdav.minutes": "分鐘", "webdav.minutes": "分鐘",
"webdav.restore.button": "從 WebDAV 恢復", "webdav.restore.button": "從 WebDAV 恢復",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "WebDAV 使用者名稱" "webdav.user": "WebDAV 使用者名稱",
"webdav.syncStatus": "同步狀態",
"webdav.autoSync.off": "關閉",
"webdav.noSync": "等待下次同步",
"webdav.syncError": "同步錯誤",
"webdav.lastSync": "上次同步時間"
}, },
"display.title": "顯示設定", "display.title": "顯示設定",
"font_size.title": "訊息字體大小", "font_size.title": "訊息字體大小",

View File

@ -1,5 +1,6 @@
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons' import { FolderOpenOutlined, SaveOutlined, SyncOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService' import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
@ -11,7 +12,8 @@ import {
setWebdavSyncInterval as _setWebdavSyncInterval, setWebdavSyncInterval as _setWebdavSyncInterval,
setWebdavUser as _setWebdavUser setWebdavUser as _setWebdavUser
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { Button, Input, Select, Switch } from 'antd' import { Button, Input, Select } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -32,7 +34,6 @@ const WebDavSettings: FC = () => {
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 [autoSync, setAutoSync] = useState<boolean>(webDAVAutoSync)
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval) const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
const [backuping, setBackuping] = useState(false) const [backuping, setBackuping] = useState(false)
@ -42,6 +43,8 @@ const WebDavSettings: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { webdavSync } = useRuntime()
// 把之前备份的文件定时上传到 webdav首先先配置 webdav 的 host, port, user, pass, path // 把之前备份的文件定时上传到 webdav首先先配置 webdav 的 host, port, user, pass, path
const onBackup = async () => { const onBackup = async () => {
@ -64,18 +67,40 @@ const WebDavSettings: FC = () => {
setRestoring(false) setRestoring(false)
} }
const onToggleAutoSync = (checked: boolean) => {
dispatch(setWebdavAutoSync(checked))
if (checked) {
startAutoSync()
} else {
stopAutoSync()
}
}
const onSyncIntervalChange = (value: number) => { const onSyncIntervalChange = (value: number) => {
setSyncInterval(value) setSyncInterval(value)
dispatch(_setWebdavSyncInterval(value)) dispatch(_setWebdavSyncInterval(value))
if (value === 0) {
dispatch(setWebdavAutoSync(false))
stopAutoSync()
} else {
dispatch(setWebdavAutoSync(true))
startAutoSync()
}
}
const renderSyncStatus = () => {
if (!webdavHost) return null
if (!webdavSync.lastSyncTime && !webdavSync.syncing && !webdavSync.lastSyncError) {
return <span style={{ color: 'var(--text-secondary)' }}>{t('settings.data.webdav.noSync')}</span>
}
return (
<HStack gap="5px" alignItems="center">
{webdavSync.syncing && <SyncOutlined spin />}
{webdavSync.lastSyncTime && (
<span style={{ color: 'var(--text-secondary)' }}>
{t('settings.data.webdav.lastSync')}: {dayjs(webdavSync.lastSyncTime).format('HH:mm:ss')}
</span>
)}
{webdavSync.lastSyncError && (
<span style={{ color: 'var(--error-color)' }}>
{t('settings.data.webdav.syncError')}: {webdavSync.lastSyncError}
</span>
)}
</HStack>
)
} }
return ( return (
@ -127,32 +152,6 @@ const WebDavSettings: FC = () => {
/> />
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.webdav.autoSync')}</SettingRowTitle>
<HStack gap="10px" alignItems="center">
<Switch
checked={autoSync}
onChange={(checked) => {
setAutoSync(checked)
onToggleAutoSync(checked)
}}
disabled={!webdavHost}
/>
<Select
value={syncInterval || 5}
onChange={onSyncIntervalChange}
disabled={!webdavHost || !autoSync}
style={{ width: 120 }}>
<Select.Option value={1}>1 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={5}>5 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={15}>15 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={30}>30 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={60}>60 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={120}>120 {t('settings.data.webdav.minutes')}</Select.Option>
</Select>
</HStack>
</SettingRow>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle> <SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between"> <HStack gap="5px" justifyContent="space-between">
@ -165,6 +164,28 @@ const WebDavSettings: FC = () => {
</Button> </Button>
</HStack> </HStack>
</SettingRow> </SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.webdav.autoSync')}</SettingRowTitle>
<Select value={syncInterval} onChange={onSyncIntervalChange} disabled={!webdavHost} style={{ width: 120 }}>
<Select.Option value={0}>{t('settings.data.webdav.autoSync.off')}</Select.Option>
<Select.Option value={1}>1 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={5}>5 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={15}>15 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={30}>30 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={60}>60 {t('settings.data.webdav.minutes')}</Select.Option>
<Select.Option value={120}>120 {t('settings.data.webdav.minutes')}</Select.Option>
</Select>
</SettingRow>
{webdavSync && syncInterval > 0 && (
<>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.webdav.syncStatus')}</SettingRowTitle>
{renderSyncStatus()}
</SettingRow>
</>
)}
</> </>
) )
} }

View File

@ -1,6 +1,7 @@
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import store from '@renderer/store' import store from '@renderer/store'
import { setWebDAVSyncState } from '@renderer/store/runtime'
import dayjs from 'dayjs' import dayjs from 'dayjs'
export async function backup() { export async function backup() {
@ -63,6 +64,9 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
console.log('[Backup] Manual backup already in progress') console.log('[Backup] Manual backup already in progress')
return return
} }
store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null }))
const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings
const backupData = await getBackupData() const backupData = await getBackupData()
@ -76,11 +80,19 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
webdavPath webdavPath
}) })
if (success) { if (success) {
store.dispatch(
setWebDAVSyncState({
lastSyncTime: Date.now(),
lastSyncError: null
})
)
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
} else { } else {
store.dispatch(setWebDAVSyncState({ lastSyncError: 'Backup failed' }))
showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
} }
} catch (error: any) { } catch (error: any) {
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
console.error('[backup] backupToWebdav: Error uploading file to WebDAV:', error) console.error('[backup] backupToWebdav: Error uploading file to WebDAV:', error)
showMessage && showMessage &&
window.modal.error({ window.modal.error({
@ -88,6 +100,7 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
content: error.message content: error.message
}) })
} finally { } finally {
store.dispatch(setWebDAVSyncState({ syncing: false }))
isManualBackupRunning = false isManualBackupRunning = false
} }
} }
@ -125,9 +138,9 @@ export function startAutoSync() {
return return
} }
const { webdavAutoSync, webdavHost, webdavSyncInterval } = store.getState().settings const { webdavAutoSync, webdavHost } = store.getState().settings
if (!webdavAutoSync || !webdavHost || webdavSyncInterval <= 0) { if (!webdavAutoSync || !webdavHost) {
console.log('[AutoSync] Invalid sync settings, auto sync disabled') console.log('[AutoSync] Invalid sync settings, auto sync disabled')
return return
} }
@ -144,7 +157,16 @@ export function startAutoSync() {
syncTimeout = null syncTimeout = null
} }
const { webdavSyncInterval } = store.getState().settings
if (webdavSyncInterval <= 0) {
console.log('[AutoSync] Invalid sync interval, auto sync disabled')
stopAutoSync()
return
}
syncTimeout = setTimeout(performAutoBackup, webdavSyncInterval * 60 * 1000) syncTimeout = setTimeout(performAutoBackup, webdavSyncInterval * 60 * 1000)
console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`) console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`)
} }

View File

@ -10,6 +10,12 @@ export interface UpdateState {
available: boolean available: boolean
} }
export interface WebDAVSyncState {
lastSyncTime: number | null
syncing: boolean
lastSyncError: string | null
}
export interface RuntimeState { export interface RuntimeState {
avatar: string avatar: string
generating: boolean generating: boolean
@ -17,6 +23,7 @@ export interface RuntimeState {
searching: boolean searching: boolean
filesPath: string filesPath: string
update: UpdateState update: UpdateState
webdavSync: WebDAVSyncState
} }
const initialState: RuntimeState = { const initialState: RuntimeState = {
@ -31,6 +38,11 @@ const initialState: RuntimeState = {
downloading: false, downloading: false,
downloadProgress: 0, downloadProgress: 0,
available: false available: false
},
webdavSync: {
lastSyncTime: null,
syncing: false,
lastSyncError: null
} }
} }
@ -55,11 +67,21 @@ const runtimeSlice = createSlice({
}, },
setUpdateState: (state, action: PayloadAction<Partial<UpdateState>>) => { setUpdateState: (state, action: PayloadAction<Partial<UpdateState>>) => {
state.update = { ...state.update, ...action.payload } state.update = { ...state.update, ...action.payload }
},
setWebDAVSyncState: (state, action: PayloadAction<Partial<WebDAVSyncState>>) => {
state.webdavSync = { ...state.webdavSync, ...action.payload }
} }
} }
}) })
export const { setAvatar, setGenerating, setMinappShow, setSearching, setFilesPath, setUpdateState } = export const {
runtimeSlice.actions setAvatar,
setGenerating,
setMinappShow,
setSearching,
setFilesPath,
setUpdateState,
setWebDAVSyncState
} = runtimeSlice.actions
export default runtimeSlice.reducer export default runtimeSlice.reducer