From 4bd9bae8b230825f404230f4dc60c7987e21ec75 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Tue, 15 Jul 2025 20:43:52 +0000 Subject: [PATCH] feat(client): on sources page, have new modal for sync that asks user what kind of sync they would like to run asdf --- frontend/src/pages/SourcesPage.tsx | 172 +++++++++++++++++++++++++++-- frontend/src/services/api.ts | 14 +++ 2 files changed, 178 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/SourcesPage.tsx b/frontend/src/pages/SourcesPage.tsx index 4cf4857..264ae25 100644 --- a/frontend/src/pages/SourcesPage.tsx +++ b/frontend/src/pages/SourcesPage.tsx @@ -60,6 +60,8 @@ import { Sync as SyncIcon, MoreVert as MoreVertIcon, Menu as MenuIcon, + Speed as QuickSyncIcon, + ManageSearch as DeepScanIcon, Folder as FolderIcon, Assessment as AssessmentIcon, Extension as ExtensionIcon, @@ -74,7 +76,7 @@ import { Error as CriticalIcon, } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; -import api, { queueService } from '../services/api'; +import api, { queueService, sourcesService } from '../services/api'; import { formatDistanceToNow } from 'date-fns'; import { useAuth } from '../contexts/AuthContext'; @@ -165,6 +167,11 @@ const SourcesPage: React.FC = () => { const [stoppingSync, setStoppingSync] = useState(null); const [validating, setValidating] = useState(null); const [autoRefreshing, setAutoRefreshing] = useState(false); + + // Sync modal state + const [syncModalOpen, setSyncModalOpen] = useState(false); + const [sourceToSync, setSourceToSync] = useState(null); + const [deepScanning, setDeepScanning] = useState(false); useEffect(() => { loadSources(); @@ -482,11 +489,26 @@ const SourcesPage: React.FC = () => { } }; - const handleTriggerSync = async (sourceId: string) => { - setSyncingSource(sourceId); + // Open sync modal instead of directly triggering sync + const handleOpenSyncModal = (source: Source) => { + setSourceToSync(source); + setSyncModalOpen(true); + }; + + const handleCloseSyncModal = () => { + setSyncModalOpen(false); + setSourceToSync(null); + }; + + const handleQuickSync = async () => { + if (!sourceToSync) return; + + setSyncingSource(sourceToSync.id); + handleCloseSyncModal(); + try { - await api.post(`/sources/${sourceId}/sync`); - showSnackbar('Sync started successfully', 'success'); + await sourcesService.triggerSync(sourceToSync.id); + showSnackbar('Quick sync started successfully', 'success'); setTimeout(loadSources, 1000); } catch (error: any) { console.error('Failed to trigger sync:', error); @@ -500,10 +522,34 @@ const SourcesPage: React.FC = () => { } }; + const handleDeepScan = async () => { + if (!sourceToSync) return; + + setDeepScanning(true); + handleCloseSyncModal(); + + try { + await sourcesService.triggerDeepScan(sourceToSync.id); + showSnackbar('Deep scan started successfully', 'success'); + setTimeout(loadSources, 1000); + } catch (error: any) { + console.error('Failed to trigger deep scan:', error); + if (error.response?.status === 409) { + showSnackbar('Source is already syncing', 'warning'); + } else if (error.response?.status === 400 && error.response?.data?.message?.includes('only supported for WebDAV')) { + showSnackbar('Deep scan is only supported for WebDAV sources', 'warning'); + } else { + showSnackbar('Failed to start deep scan', 'error'); + } + } finally { + setDeepScanning(false); + } + }; + const handleStopSync = async (sourceId: string) => { setStoppingSync(sourceId); try { - await api.post(`/sources/${sourceId}/sync/stop`); + await sourcesService.stopSync(sourceId); showSnackbar('Sync stopped successfully', 'success'); setTimeout(loadSources, 1000); } catch (error: any) { @@ -929,8 +975,8 @@ const SourcesPage: React.FC = () => { handleTriggerSync(source.id)} - disabled={syncingSource === source.id || !source.enabled} + onClick={() => handleOpenSyncModal(source)} + disabled={syncingSource === source.id || deepScanning || !source.enabled} sx={{ bgcolor: alpha(theme.palette.primary.main, 0.1), '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.2) }, @@ -2320,6 +2366,116 @@ const SourcesPage: React.FC = () => { + {/* Sync Type Selection Modal */} + + + + + Choose Sync Type + + + + + {sourceToSync && ( + <> + Select the type of synchronization for {sourceToSync.name}: + + )} + + + + + + + + + Quick Sync + + + Fast incremental sync using ETags. Only processes new or changed files. + + + + + + + + + + + + + + Deep Scan + + + Complete rescan that resets ETag expectations. Use for troubleshooting sync issues. + + + {sourceToSync?.source_type === 'webdav' ? ( + + ) : ( + + )} + + + + + + + {sourceToSync?.source_type !== 'webdav' && ( + + Deep scan is currently only available for WebDAV sources. Other source types will use quick sync. + + )} + + + + + + {/* Snackbar */} { + return api.post(`/sources/${sourceId}/sync`) + }, + + triggerDeepScan: (sourceId: string) => { + return api.post(`/sources/${sourceId}/deep-scan`) + }, + + stopSync: (sourceId: string) => { + return api.post(`/sources/${sourceId}/sync/stop`) + }, } \ No newline at end of file