mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 14:29:15 +08:00
feat: add sync status show
This commit is contained in:
parent
63cdc15bc2
commit
1d3a01dd49
@ -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",
|
||||||
|
|||||||
@ -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": "メッセージのフォントサイズ",
|
||||||
|
|||||||
@ -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": "Размер шрифта сообщений",
|
||||||
|
|||||||
@ -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": "消息字体大小",
|
||||||
|
|||||||
@ -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": "訊息字體大小",
|
||||||
|
|||||||
@ -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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user