feat(ui): show in the UI the sync URL that would be hit
This commit is contained in:
parent
8cd859d38e
commit
76e4562f77
|
|
@ -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 (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 2,
|
||||||
|
bgcolor: alpha(theme.palette.background.default, 0.5),
|
||||||
|
border: `1px solid ${alpha(theme.palette.divider, 0.3)}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
|
||||||
|
Example sync URL:
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
p: 1.5,
|
||||||
|
bgcolor: alpha(theme.palette.common.black, 0.02),
|
||||||
|
borderRadius: 1,
|
||||||
|
mb: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{urlParts.parts.map((part, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
color: getColorForType(part.type),
|
||||||
|
fontWeight: part.type === 'folder' ? 600 : 400,
|
||||||
|
textDecoration: part.type === 'folder' ? 'underline' : 'none',
|
||||||
|
textDecorationStyle: part.type === 'folder' ? 'dotted' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{part.text}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
<Stack direction="row" spacing={2} flexWrap="wrap" useFlexGap>
|
||||||
|
{uniqueTypes.map((type) => (
|
||||||
|
<Stack key={type} direction="row" alignItems="center" spacing={0.5}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: '50%',
|
||||||
|
bgcolor: getColorForType(type),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{getLabelForType(type)}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreateSource = () => {
|
const handleCreateSource = () => {
|
||||||
setEditingSource(null);
|
setEditingSource(null);
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -1678,6 +1840,9 @@ const SourcesPage: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* URL Preview */}
|
||||||
|
<UrlPreviewBox />
|
||||||
|
|
||||||
{/* File Extensions */}
|
{/* File Extensions */}
|
||||||
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
@ -2027,6 +2192,9 @@ const SourcesPage: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* URL Preview */}
|
||||||
|
<UrlPreviewBox />
|
||||||
|
|
||||||
{/* File Extensions */}
|
{/* File Extensions */}
|
||||||
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
@ -2280,6 +2448,9 @@ const SourcesPage: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* URL Preview */}
|
||||||
|
<UrlPreviewBox />
|
||||||
|
|
||||||
{/* File Extensions */}
|
{/* File Extensions */}
|
||||||
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue