diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index 3a69668f99..e2e633d557 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -14,6 +14,7 @@ import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte' import ResourceForm from './ResourceForm.svelte' import { managedCredentialHost } from './git_sync/managedCredential' + import ReplaceGitCredential from './git_sync/ReplaceGitCredential.svelte' import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte' import Alert from './common/alert/Alert.svelte' import { resource } from 'runed' @@ -151,6 +152,16 @@ let current = $derived(selected ? states[selected]?.draft : undefined) let managedHost = $derived(managedCredentialHost(current?.args)) + // The saved URL, not the draft's: a credential is bound to the repository it + // is issued for, so binding one to an edit that has not landed yet would tie + // it to something the resource does not point at. + let deployedUrl = $derived( + selected ? ((fetchedResources[selected]?.value as any)?.url as string | undefined) : undefined + ) + // Only an unsaved *URL* blocks replacing the token, not any unsaved change: + // opening the drawer materialises schema defaults (`folder: ""`), so a whole- + // resource dirty check would disable it the moment the drawer opens. + let urlDirty = $derived(!!deployedUrl && current?.args?.url !== deployedUrl) let resourceToEdit: Resource | undefined = $derived( selected ? fetchedResources[selected] : undefined ) @@ -412,12 +423,24 @@ {/if} - {#if managedHost} + {#if managedHost && selected} - The URL below carries no credential. Windmill renews the token before it expires and hands - it to this workspace's sync jobs, and forks of this workspace use it without storing their - own copy. To replace it, pick the project again with the - {managedHost === 'gitlab' ? 'GitLab' : 'git'} button below. +
+
+ The URL below carries no credential. Windmill renews the token before it expires and + hands it to this workspace's sync jobs, and forks of this workspace use it without + storing their own copy. + {#if urlDirty} + Save the URL change to replace the token. + {/if} +
+ +
{/if} diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index a958ffc7f8..ccfffe19a4 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -226,9 +226,15 @@ return { type: days <= 7 ? ('error' as const) : days <= 14 ? ('warning' as const) : ('info' as const), title: `Repository token ${when}`, - body: canSelfRotate - ? 'Windmill cannot renew it because it cannot write the new token back to where this URL is stored. Move the URL into a Windmill variable, or replace the token before it expires.' - : 'Give the token the api or self_rotate scope and Windmill will renew it on its own. Otherwise, replace it before it expires to keep sync running.' + body: + (canSelfRotate + ? 'Windmill cannot renew it because it cannot write the new token back to where this URL is stored. Move the URL into a Windmill variable, or replace the token before it expires.' + : 'Give the token the api or self_rotate scope and Windmill will renew it on its own. Otherwise, replace it before it expires to keep sync running.') + + // The remedy lives with the credential, which the resource owns; saying + // where stops the warning being a dead end. + (managedCredential + ? ` Replace it on the ${repo?.git_repo_resource_path?.replace(/^\$res:/, '') ?? 'repository'} resource.` + : '') } }) diff --git a/frontend/src/lib/components/git_sync/ReplaceGitCredential.svelte b/frontend/src/lib/components/git_sync/ReplaceGitCredential.svelte new file mode 100644 index 0000000000..e437d34b19 --- /dev/null +++ b/frontend/src/lib/components/git_sync/ReplaceGitCredential.svelte @@ -0,0 +1,116 @@ + + + + {#snippet trigger()} + + {/snippet} + {#snippet content({ close })} +
+
+
+
New access token
+
+ For the same repository. Windmill stores it in place of the current one and renews it + from then on. +
+ +
+ {#if error} + {error} + {/if} +
+ +
+
+
+ {/snippet} +