mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 08:04:25 +00:00
refactor: ask only whether the token gets renewed, not why it does not
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
This commit is contained in:
co-authored by
Claude Opus 5
parent
4e4c5c2c8f
commit
01cbbfec56
@@ -1 +1 @@
|
||||
3f2b5fad783c9ae099a8f9ccf712b100f822c956
|
||||
7b607d736cc23f0ff845599fd1ac9fad4c4fd307
|
||||
@@ -34353,6 +34353,13 @@ components:
|
||||
description: one-way digest identifying which credential this status describes
|
||||
rotatable:
|
||||
type: boolean
|
||||
description: whether this workspace renews the credential itself
|
||||
renewed:
|
||||
type: boolean
|
||||
description: >-
|
||||
whether the credential is renewed at all, by whichever workspace
|
||||
holds it; true for a fork reading an ancestor's, which nothing local
|
||||
renews but nothing needs to
|
||||
checked_at:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
@@ -441,12 +441,21 @@ pub struct GitCredentialStatus {
|
||||
/// different credential here", which look identical at the API otherwise.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub token_fingerprint: Option<String>,
|
||||
/// Whether Windmill renews this credential itself. That needs a scope which
|
||||
/// Whether *this workspace* renews the credential. That needs a scope which
|
||||
/// permits it (`api` or `self_rotate`) and a credential this workspace holds:
|
||||
/// a token carried in the repository URL is the operator's to manage, and one
|
||||
/// resolved from an ancestor is the ancestor's, so neither is renewed here.
|
||||
/// `scopes` therefore only tells the scope half apart.
|
||||
pub rotatable: bool,
|
||||
/// Whether the credential gets renewed at all, by whichever workspace holds
|
||||
/// it. Equals `rotatable` for the holder and is also true for a fork that
|
||||
/// borrows an ancestor's, which nothing local renews but nothing needs to.
|
||||
///
|
||||
/// The UI shows expiry warnings on exactly `!renewed`, so this is what keeps
|
||||
/// a fork from nagging about a token its ancestor renews on schedule. Kept
|
||||
/// server-side because only the server can tell a borrowed credential from an
|
||||
/// unheld one, which the resource alone cannot express.
|
||||
#[serde(default)]
|
||||
pub renewed: bool,
|
||||
/// Unix timestamp (seconds) of the last check.
|
||||
pub checked_at: i64,
|
||||
/// Why the last check or rotation failed, cleared by the next success.
|
||||
|
||||
@@ -155,34 +155,6 @@
|
||||
/** The host named by the resource's `managed_credential`, when Windmill holds
|
||||
* the repository's token rather than it being written into the URL. */
|
||||
let managedCredential = $state<string | undefined>(undefined)
|
||||
let canSelfRotate = $derived(
|
||||
(repo?.credential?.scopes ?? []).some((s) => s === 'api' || s === 'self_rotate')
|
||||
)
|
||||
/**
|
||||
* Why this repository's token is or is not being renewed.
|
||||
*
|
||||
* Renewal needs a scope that permits self-rotation and a credential the
|
||||
* workspace holds, and the alert and the quiet status line below both describe
|
||||
* that state at different day counts. Reading it independently in each has
|
||||
* twice produced contradictory advice, so it is classified once here and both
|
||||
* render the answer.
|
||||
*
|
||||
* `inherited` is reachable only on a fork: forking copies the resource's
|
||||
* marker but not the credential, so the fork borrows its ancestor's and reports
|
||||
* `rotatable: false` while the ancestor renews it on schedule. Any other
|
||||
* workspace whose stored credential stopped matching its URL loses its recorded
|
||||
* status entirely and never reaches here.
|
||||
*/
|
||||
let renewal = $derived.by(() => {
|
||||
const credential = repo?.credential
|
||||
if (!credential) return undefined
|
||||
if (credential.rotatable) return $enterpriseLicense ? 'renewed' : 'needs-license'
|
||||
// The marker comes from the resource, so until that load lands there is no
|
||||
// answer yet and guessing one would state the opposite of the truth.
|
||||
if (loadingResourceInfo) return 'unknown'
|
||||
if (canSelfRotate) return managedCredential ? 'inherited' : 'not-held'
|
||||
return managedCredential ? 'no-scope' : 'no-scope-and-not-held'
|
||||
})
|
||||
// Whether Windmill itself holds a credential for the repository, which is
|
||||
// what the managed features (webhooks, pull requests, commit checks) need.
|
||||
// A GitHub App installation qualifies, and so does a token the server keeps.
|
||||
@@ -233,7 +205,7 @@
|
||||
if (days === undefined) return undefined
|
||||
const when =
|
||||
days <= 0 ? 'has expired' : days === 1 ? 'expires tomorrow' : `expires in ${days} days`
|
||||
if (credential.rotatable) {
|
||||
if (credential.renewed) {
|
||||
// A token Windmill renews needs no countdown: a renewal that fails records
|
||||
// an error, which is handled above. Reaching the expiry date anyway is the
|
||||
// one state that proves renewal never happened, and it is the only one
|
||||
@@ -247,25 +219,14 @@
|
||||
}
|
||||
}
|
||||
if (days > 30) return undefined
|
||||
// Nothing for this workspace to act on: the holder renews it, and telling a
|
||||
// fork admin to replace it would split the credential in two.
|
||||
if (renewal === 'inherited' || renewal === 'unknown') return undefined
|
||||
const remedy =
|
||||
renewal === 'no-scope'
|
||||
? // The remedy lives with the credential, which the resource owns; saying
|
||||
// where stops the warning being a dead end.
|
||||
` Replace it on the ${repo?.git_repo_resource_path?.replace(/^\$res:/, '') ?? 'repository'} resource.`
|
||||
: ' Connect the repository with the GitLab button to hand the token to Windmill, or replace it before it expires.'
|
||||
const cause =
|
||||
renewal === 'not-held'
|
||||
? 'Windmill is not the holder of this token, so it does not renew it.'
|
||||
: renewal === 'no-scope'
|
||||
? 'Give the token the api or self_rotate scope so Windmill can renew it.'
|
||||
: 'Windmill is not the holder of this token, and could not renew it anyway without the api or self_rotate scope.'
|
||||
// Why nothing renews it is the server's answer to give, not this card's to
|
||||
// infer: it alone can tell a token the operator owns from one an ancestor
|
||||
// holds and renews. The card asks only whether anything renews it, and says
|
||||
// where to act when nothing does.
|
||||
return {
|
||||
type: days <= 7 ? ('error' as const) : days <= 14 ? ('warning' as const) : ('info' as const),
|
||||
title: `Repository token ${when}`,
|
||||
body: cause + remedy
|
||||
body: `Windmill does not renew this token. Replace it on the ${repo?.git_repo_resource_path?.replace(/^\$res:/, '') ?? 'repository'} resource before it expires.`
|
||||
}
|
||||
})
|
||||
|
||||
@@ -665,26 +626,14 @@
|
||||
<div class="text-xs text-secondary">
|
||||
{#if credentialDaysLeft === undefined}
|
||||
Repository token does not expire.
|
||||
{:else if renewal === 'renewed'}
|
||||
{:else if repo.credential.renewed && $enterpriseLicense}
|
||||
Repository token expires on {repo.credential.expires_at}, and Windmill renews it
|
||||
automatically.
|
||||
{:else if renewal === 'needs-license'}
|
||||
{:else if repo.credential.renewed}
|
||||
Repository token expires on {repo.credential.expires_at}. Renewing it automatically
|
||||
requires an enterprise license.
|
||||
{:else if renewal === 'inherited'}
|
||||
Repository token expires on {repo.credential.expires_at}, and the workspace that holds it
|
||||
renews it.
|
||||
{:else if renewal === 'not-held'}
|
||||
Repository token expires on {repo.credential.expires_at}. Windmill renews only a token it
|
||||
holds, and it does not hold this one.
|
||||
{:else if renewal === 'no-scope'}
|
||||
Repository token expires on {repo.credential.expires_at}. Give it the api or self_rotate
|
||||
scope so Windmill can renew it.
|
||||
{:else if renewal === 'no-scope-and-not-held'}
|
||||
Repository token expires on {repo.credential.expires_at}. Windmill renews only a token it
|
||||
holds, and this one would also need the api or self_rotate scope.
|
||||
{:else}
|
||||
Repository token expires on {repo.credential.expires_at}.
|
||||
Repository token expires on {repo.credential.expires_at}, and Windmill does not renew it.
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user