feat: add sync status show
This commit is contained in:
parent
63cdc15bc2
commit
1d3a01dd49
@ -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",
|
||||
|
||||
@ -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": "メッセージのフォントサイズ",
|
||||
|
||||
@ -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": "Размер шрифта сообщений",
|
||||
|
||||
@ -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": "消息字体大小",
|
||||
|
||||
@ -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": "訊息字體大小",
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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`)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user