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.restore.button": "Restore from 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",
"font_size.title": "Message font size",

View File

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

View File

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

View File

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

View File

@ -362,7 +362,12 @@
"webdav.minutes": "分鐘",
"webdav.restore.button": "從 WebDAV 恢復",
"webdav.title": "WebDAV",
"webdav.user": "WebDAV 使用者名稱"
"webdav.user": "WebDAV 使用者名稱",
"webdav.syncStatus": "同步狀態",
"webdav.autoSync.off": "關閉",
"webdav.noSync": "等待下次同步",
"webdav.syncError": "同步錯誤",
"webdav.lastSync": "上次同步時間"
},
"display.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 { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { backupToWebdav, restoreFromWebdav, startAutoSync, stopAutoSync } from '@renderer/services/BackupService'
import { useAppDispatch } from '@renderer/store'
@ -11,7 +12,8 @@ import {
setWebdavSyncInterval as _setWebdavSyncInterval,
setWebdavUser as _setWebdavUser
} 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 { useTranslation } from 'react-i18next'
@ -32,7 +34,6 @@ const WebDavSettings: FC = () => {
const [webdavPass, setWebdavPass] = useState<string | undefined>(webDAVPass)
const [webdavPath, setWebdavPath] = useState<string | undefined>(webDAVPath)
const [autoSync, setAutoSync] = useState<boolean>(webDAVAutoSync)
const [syncInterval, setSyncInterval] = useState<number>(webDAVSyncInterval)
const [backuping, setBackuping] = useState(false)
@ -42,6 +43,8 @@ const WebDavSettings: FC = () => {
const { t } = useTranslation()
const { webdavSync } = useRuntime()
// 把之前备份的文件定时上传到 webdav首先先配置 webdav 的 host, port, user, pass, path
const onBackup = async () => {
@ -64,18 +67,40 @@ const WebDavSettings: FC = () => {
setRestoring(false)
}
const onToggleAutoSync = (checked: boolean) => {
dispatch(setWebdavAutoSync(checked))
if (checked) {
startAutoSync()
} else {
stopAutoSync()
}
}
const onSyncIntervalChange = (value: number) => {
setSyncInterval(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 (
@ -127,32 +152,6 @@ const WebDavSettings: FC = () => {
/>
</SettingRow>
<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>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
@ -165,6 +164,28 @@ const WebDavSettings: FC = () => {
</Button>
</HStack>
</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 i18n from '@renderer/i18n'
import store from '@renderer/store'
import { setWebDAVSyncState } from '@renderer/store/runtime'
import dayjs from 'dayjs'
export async function backup() {
@ -63,6 +64,9 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
console.log('[Backup] Manual backup already in progress')
return
}
store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null }))
const { webdavHost, webdavUser, webdavPass, webdavPath } = store.getState().settings
const backupData = await getBackupData()
@ -76,11 +80,19 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
webdavPath
})
if (success) {
store.dispatch(
setWebDAVSyncState({
lastSyncTime: Date.now(),
lastSyncError: null
})
)
showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' })
} else {
store.dispatch(setWebDAVSyncState({ lastSyncError: 'Backup failed' }))
showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' })
}
} catch (error: any) {
store.dispatch(setWebDAVSyncState({ lastSyncError: error.message }))
console.error('[backup] backupToWebdav: Error uploading file to WebDAV:', error)
showMessage &&
window.modal.error({
@ -88,6 +100,7 @@ export async function backupToWebdav({ showMessage = true }: { showMessage?: boo
content: error.message
})
} finally {
store.dispatch(setWebDAVSyncState({ syncing: false }))
isManualBackupRunning = false
}
}
@ -125,9 +138,9 @@ export function startAutoSync() {
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')
return
}
@ -144,7 +157,16 @@ export function startAutoSync() {
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)
console.log(`[AutoSync] Next sync scheduled in ${webdavSyncInterval} minutes`)
}

View File

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