From ea38419353984eda88c6413e71e08e223ff5ef94 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Fri, 20 Feb 2026 20:36:10 +0000 Subject: [PATCH] add breadcrumb navigation to advanced setup mode (#8010) * feat: add breadcrumb navigation to advanced setup mode on first-time page The advanced setup mode on /user/first-time lacked a step indicator, making navigation disorienting. This adds a 2-step breadcrumb ("Settings" / "Root login & Resource Types") with step-aware navigation buttons and extracts the account setup UI into a reusable snippet shared by both wizard and advanced modes. Co-Authored-By: Claude Opus 4.6 * fix: address review issues in advanced setup breadcrumb - Gate resource type sync by mode to prevent early trigger - Reset yamlMode when advancing to account setup step - Allow forward navigation via breadcrumb click - Use saveAndProceed on Back button for consistency Co-Authored-By: Claude Opus 4.6 * fix: show EE license key warning in advanced setup mode Generalize proceedFromCore to trigger the license key warning when leaving the settings step in both wizard (step 0) and full mode (fullStep 0), including the Continue button and breadcrumb forward navigation. Co-Authored-By: Claude Opus 4.6 * fix: independent scroll for sidebar and content in advanced setup Match the superadmin settings drawer pattern: the outer flex container constrains height without scrolling, while the sidebar and content area each have h-full overflow-auto for independent scrolling. Co-Authored-By: Claude Opus 4.6 * format * fix: simplify breadcrumb onselect to only handle backward navigation The Breadcrumb component disables forward buttons, so the proceedFromCore branch was unreachable. Simplify to only handle i < fullStep. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../(user)/instance_settings/+page.svelte | 327 +++++++++++------- 1 file changed, 197 insertions(+), 130 deletions(-) diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte index 07b821fe04..b5481f17fe 100644 --- a/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte @@ -33,20 +33,35 @@ const wizardStepLabels = [...settingsSteps.map((s) => s.label), 'Root login & Resource Types'] + const fullStepLabels = ['Settings', 'Root login & Resource Types'] + const initialMode = $page.url.searchParams.get('mode') === 'full' ? 'full' : 'wizard' const initialStep = Math.max( 0, Math.min(parseInt($page.url.searchParams.get('step') ?? '0') || 0, wizardStepLabels.length - 1) ) + const initialFullStep = + initialMode === 'full' + ? Math.max( + 0, + Math.min( + parseInt($page.url.searchParams.get('step') ?? '0') || 0, + fullStepLabels.length - 1 + ) + ) + : 0 let mode: 'wizard' | 'full' = $state(initialMode) let wizardStep = $state(initialStep) + let fullStep = $state(initialFullStep) $effect(() => { const url = new URL(window.location.href) if (mode === 'wizard') { url.searchParams.set('step', String(wizardStep)) + url.searchParams.delete('mode') } else { - url.searchParams.delete('step') + url.searchParams.set('step', String(fullStep)) + url.searchParams.set('mode', 'full') } history.replaceState(history.state, '', url) }) @@ -92,7 +107,10 @@ } $effect(() => { - if (!isSettingsStep(wizardStep) && rtSyncStatus === 'idle') { + if ( + rtSyncStatus === 'idle' && + ((mode === 'wizard' && !isSettingsStep(wizardStep)) || (mode === 'full' && fullStep === 1)) + ) { syncCachedResourceTypes() } }) @@ -183,7 +201,9 @@ /** Check if we need to warn about missing EE license key before proceeding */ function proceedFromCore(callback: () => void) { - if (wizardStep === 0 && isEeImage() && isLicenseKeyEmpty()) { + const leavingSettings = + (mode === 'wizard' && wizardStep === 0) || (mode === 'full' && fullStep === 0) + if (leavingSettings && isEeImage() && isLicenseKeyEmpty()) { pendingNextCallback = callback showLicenseKeyWarning = true return @@ -215,6 +235,7 @@ function switchToWizardMode() { yamlMode = false + fullStep = 0 mode = 'wizard' } @@ -302,6 +323,100 @@ } +{#snippet accountSetupContent()} + + +
+ +
+
+ Email + 0 && !emailValid ? 'Must be a valid email' : undefined} + size="md" + /> + {#if $superadmin} +

Current email: {$superadmin}

+ {/if} +
+
+ Password + 0 && !passwordValid + ? 'Must be at least 2 characters' + : undefined} + size="md" + /> +
+
+
+ + +
+ {#if rtSyncStatus === 'loading'} + + {:else if rtSyncStatus === 'success'} + + {rtSyncMessage} + + {:else if rtSyncStatus === 'error'} + + {rtSyncMessage} + + {/if} + +
+ +

+ Fetches the latest resource types directly from the Windmill Hub (requires internet + access). +

+
+ {#if hubSyncStatus === 'success'} + + {hubSyncMessage} + + {:else if hubSyncStatus === 'error'} + + {hubSyncMessage} + + {/if} + +

+ The daily schedule synchronizes resource types from the Hub every day at midnight UTC. +

+
+
+ + {#if accountError} + + {accountError} + + {/if} +
+{/snippet} +
{#if mode === 'wizard'} @@ -339,136 +454,69 @@ /> {/key} {:else} - - - -
- -
-
- Email - 0 && !emailValid ? 'Must be a valid email' : undefined} - size="md" - /> - {#if $superadmin} -

Current email: {$superadmin}

- {/if} -
-
- Password - 0 && !passwordValid - ? 'Must be at least 2 characters' - : undefined} - size="md" - /> -
-
-
- - -
- {#if rtSyncStatus === 'loading'} - - {:else if rtSyncStatus === 'success'} - - {rtSyncMessage} - - {:else if rtSyncStatus === 'error'} - - {rtSyncMessage} - - {/if} - -
- -

- Fetches the latest resource types directly from the Windmill Hub (requires - internet access). -

-
- {#if hubSyncStatus === 'success'} - - {hubSyncMessage} - - {:else if hubSyncStatus === 'error'} - - {hubSyncMessage} - - {/if} - -

- The daily schedule synchronizes resource types from the Hub every day at midnight - UTC. -

-
-
- - {#if accountError} - - {accountError} - - {/if} -
+ {@render accountSetupContent()} {/if}
{:else} - -
- + +
+ { + if (i < fullStep) { + saveAndProceed(() => { + yamlMode = false + fullStep = i + }) + } + }} + > + {#snippet separator()} + + {/snippet} + + {#if fullStep === 0} + + {/if}
- -
- {#if !yamlMode} -
- - + {#if fullStep === 0} +
+ {#if !yamlMode} +
+ + +
+ {/if} + +
+ { + const targetTab = categoryToTabMap[category] + if (targetTab) { + handleNavigate(targetTab) + } + }} />
- {/if} - -
- { - const targetTab = categoryToTabMap[category] - if (targetTab) { - handleNavigate(targetTab) - } - }} - />
-
+ {:else} +
+ {@render accountSetupContent()} +
+ {/if} {/if} @@ -517,7 +565,7 @@ {/if}
- {:else} + {:else if fullStep === 0} + Continue + + {:else} + + {/if}