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.
+