From 76e4562f7772912560e33b6371c40db40a469f06 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Thu, 11 Dec 2025 08:49:55 -0800 Subject: [PATCH 1/2] feat(ui): show in the UI the sync URL that would be hit --- frontend/src/pages/SourcesPage.tsx | 227 +++++++++++++++++++++++++---- 1 file changed, 199 insertions(+), 28 deletions(-) diff --git a/frontend/src/pages/SourcesPage.tsx b/frontend/src/pages/SourcesPage.tsx index e57c277..24a5879 100644 --- a/frontend/src/pages/SourcesPage.tsx +++ b/frontend/src/pages/SourcesPage.tsx @@ -279,6 +279,168 @@ const SourcesPage: React.FC = () => { } }; + // Helper function to build example sync URL based on source type and configuration + const buildExampleSyncUrl = (): { parts: { text: string; type: 'server' | 'path' | 'folder' | 'file' }[] } | null => { + const exampleFile = 'document1.pdf'; + const firstFolder = formData.watch_folders.length > 0 ? formData.watch_folders[0] : '/Documents'; + + if (formData.source_type === 'webdav') { + if (!formData.server_url) return null; + + let serverUrl = formData.server_url.trim(); + // Add https:// if no protocol specified + if (!serverUrl.startsWith('http://') && !serverUrl.startsWith('https://')) { + serverUrl = `https://${serverUrl}`; + } + serverUrl = serverUrl.replace(/\/+$/, ''); // Remove trailing slashes + + let webdavPath = ''; + if (formData.server_type === 'nextcloud') { + // Nextcloud uses /remote.php/dav/files/{username} + if (!serverUrl.includes('/remote.php/dav/files/')) { + webdavPath = `/remote.php/dav/files/${formData.username || 'username'}`; + } + } else if (formData.server_type === 'owncloud') { + // ownCloud uses /remote.php/webdav + if (!serverUrl.includes('/remote.php/webdav')) { + webdavPath = '/remote.php/webdav'; + } + } + // For generic, use the URL as-is + + const cleanFolder = firstFolder.replace(/^\/+/, ''); // Remove leading slashes + + return { + parts: [ + { text: serverUrl, type: 'server' }, + { text: webdavPath, type: 'path' }, + { text: `/${cleanFolder}`, type: 'folder' }, + { text: `/${exampleFile}`, type: 'file' }, + ], + }; + } else if (formData.source_type === 's3') { + if (!formData.bucket_name) return null; + + const endpoint = formData.endpoint_url?.trim() || `https://s3.${formData.region || 'us-east-1'}.amazonaws.com`; + const cleanEndpoint = endpoint.replace(/\/+$/, ''); + const prefix = formData.prefix?.trim().replace(/^\/+|\/+$/g, '') || ''; + const cleanFolder = firstFolder.replace(/^\/+|\/+$/, ''); + + const parts: { text: string; type: 'server' | 'path' | 'folder' | 'file' }[] = [ + { text: cleanEndpoint, type: 'server' }, + { text: `/${formData.bucket_name}`, type: 'path' }, + { text: `/${cleanFolder}`, type: 'folder' }, + { text: `/${exampleFile}`, type: 'file' }, + ]; + // Insert prefix after bucket if present + if (prefix) { + parts.splice(2, 0, { text: `/${prefix}`, type: 'path' }); + } + + return { parts }; + } else if (formData.source_type === 'local_folder') { + if (formData.watch_folders.length === 0) return null; + + return { + parts: [ + { text: firstFolder, type: 'folder' }, + { text: `/${exampleFile}`, type: 'file' }, + ], + }; + } + + return null; + }; + + // URL Preview Component + const UrlPreviewBox = () => { + const urlParts = buildExampleSyncUrl(); + + if (!urlParts) return null; + + const getColorForType = (type: 'server' | 'path' | 'folder' | 'file') => { + switch (type) { + case 'server': return theme.palette.primary.main; + case 'path': return theme.palette.info.main; + case 'folder': return theme.palette.success.main; + case 'file': return theme.palette.text.secondary; + default: return theme.palette.text.primary; + } + }; + + const getLabelForType = (type: 'server' | 'path' | 'folder' | 'file') => { + switch (type) { + case 'server': return 'Server URL'; + case 'path': return formData.source_type === 'webdav' ? 'WebDAV Path' : 'Bucket/Prefix'; + case 'folder': return 'Watch Directory'; + case 'file': return 'Example File'; + default: return ''; + } + }; + + // Get unique types for legend + const uniqueTypes = Array.from(new Set(urlParts.parts.map(p => p.type))); + + return ( + + + Example sync URL: + + + {urlParts.parts.map((part, index) => ( + + {part.text} + + ))} + + + {uniqueTypes.map((type) => ( + + + + {getLabelForType(type)} + + + ))} + + + ); + }; + const handleCreateSource = () => { setEditingSource(null); setFormData({ @@ -1635,7 +1797,7 @@ const SourcesPage: React.FC = () => { Folders to Monitor - + Specify which folders to scan for files. Use absolute paths starting with "/". @@ -1646,14 +1808,14 @@ const SourcesPage: React.FC = () => { value={newFolder} onChange={(e) => setNewFolder(e.target.value)} placeholder="/Documents" - sx={{ + sx={{ flexGrow: 1, - '& .MuiOutlinedInput-root': { borderRadius: 2 } + '& .MuiOutlinedInput-root': { borderRadius: 2 } }} /> -