diff --git a/frontend-modern/src/components/Settings/GuestURLs.tsx b/frontend-modern/src/components/Settings/GuestURLs.tsx index 3862392f2..7edee9c5e 100644 --- a/frontend-modern/src/components/Settings/GuestURLs.tsx +++ b/frontend-modern/src/components/Settings/GuestURLs.tsx @@ -17,6 +17,7 @@ export function GuestURLs(props: GuestURLsProps) { const [searchTerm, setSearchTerm] = createSignal(''); const [loading, setLoading] = createSignal(false); const [initialLoad, setInitialLoad] = createSignal(true); + const [urlErrors, setUrlErrors] = createSignal>({}); // Combine VMs and containers into a single list const allGuests = createMemo(() => { @@ -76,18 +77,29 @@ export function GuestURLs(props: GuestURLsProps) { setLoading(true); try { const metadata = guestMetadata(); - const promises: Promise[] = []; + const errors: string[] = []; // Update each guest that has changes for (const [guestId, meta] of Object.entries(metadata)) { if (meta.customUrl !== undefined) { - promises.push(GuestMetadataAPI.updateMetadata(guestId, { customUrl: meta.customUrl })); + try { + await GuestMetadataAPI.updateMetadata(guestId, { customUrl: meta.customUrl }); + } catch (err: any) { + // Extract error message from response + const errorMsg = err.message || err.toString(); + errors.push(`${guestId}: ${errorMsg}`); + console.error(`Failed to save URL for ${guestId}:`, err); + } } } - await Promise.all(promises); - showSuccess('Guest URLs saved'); - props.setHasUnsavedChanges(false); + if (errors.length > 0) { + // Show specific validation errors + showError(errors.join('\n')); + } else { + showSuccess('Guest URLs saved'); + props.setHasUnsavedChanges(false); + } } catch (err) { console.error('Failed to save guest URLs:', err); showError('Failed to save guest URLs'); @@ -96,6 +108,39 @@ export function GuestURLs(props: GuestURLsProps) { } }; + // Validate URL format + const validateURL = (url: string): string | null => { + if (!url) return null; // Empty is valid + + // Check for incomplete URLs like "https://emby." + if (url.endsWith('.') && !url.includes('..')) { + return 'URL appears incomplete - please enter a complete domain or IP address'; + } + + // Check for missing protocol + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return 'URL must start with http:// or https://'; + } + + try { + const parsed = new URL(url); + + // Check for valid host + if (!parsed.hostname) { + return 'URL must include a valid hostname or IP address'; + } + + // Check for incomplete hostnames + if (parsed.hostname.endsWith('.') && !parsed.hostname.includes('..')) { + return 'Hostname appears incomplete'; + } + + return null; // Valid + } catch (e) { + return 'Invalid URL format'; + } + }; + // Update a guest's URL configuration const updateGuestURL = (guestId: string, url: string) => { setGuestMetadata({ @@ -106,6 +151,16 @@ export function GuestURLs(props: GuestURLsProps) { } }); + // Validate and update errors + const error = validateURL(url); + const errors = { ...urlErrors() }; + if (error) { + errors[guestId] = error; + } else { + delete errors[guestId]; + } + setUrlErrors(errors); + props.setHasUnsavedChanges(true); }; @@ -154,7 +209,7 @@ export function GuestURLs(props: GuestURLsProps) { {/* Save Button */} - +