diff --git a/backend/.sqlx/query-0222c6722a85ba1a86a1535c4c8fde581934864199fa0a484c9a8b859f86ec7d.json b/backend/.sqlx/query-0222c6722a85ba1a86a1535c4c8fde581934864199fa0a484c9a8b859f86ec7d.json new file mode 100644 index 0000000000..2c42103eed --- /dev/null +++ b/backend/.sqlx/query-0222c6722a85ba1a86a1535c4c8fde581934864199fa0a484c9a8b859f86ec7d.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value FROM resource WHERE workspace_id = $1 AND path = $2 FOR UPDATE", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "0222c6722a85ba1a86a1535c4c8fde581934864199fa0a484c9a8b859f86ec7d" +} diff --git a/backend/.sqlx/query-45e23417d12e4623016f89efd5d0f24f52437daa796ee99eec77c2cef135c9f6.json b/backend/.sqlx/query-45e23417d12e4623016f89efd5d0f24f52437daa796ee99eec77c2cef135c9f6.json deleted file mode 100644 index 92d7c557b6..0000000000 --- a/backend/.sqlx/query-45e23417d12e4623016f89efd5d0f24f52437daa796ee99eec77c2cef135c9f6.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(git_sync, '{repositories}',\n (SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' = $2\n THEN CASE WHEN $3::jsonb = 'null'::jsonb\n THEN elem - 'credential'\n ELSE jsonb_set(elem, '{credential}', $3) END\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "45e23417d12e4623016f89efd5d0f24f52437daa796ee99eec77c2cef135c9f6" -} diff --git a/backend/.sqlx/query-81e7f902c40d72937b494cd9f3f8c9fe4348d179add37361353fc9a02d96f2fa.json b/backend/.sqlx/query-81e7f902c40d72937b494cd9f3f8c9fe4348d179add37361353fc9a02d96f2fa.json new file mode 100644 index 0000000000..b1c00201f9 --- /dev/null +++ b/backend/.sqlx/query-81e7f902c40d72937b494cd9f3f8c9fe4348d179add37361353fc9a02d96f2fa.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(git_sync, '{repositories}',\n COALESCE((SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' = $2\n THEN CASE WHEN $3::jsonb = 'null'::jsonb\n THEN elem - 'credential'\n ELSE jsonb_set(elem, '{credential}', $3) END\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem), '[]'::jsonb)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n AND EXISTS (\n SELECT 1 FROM jsonb_array_elements(git_sync->'repositories') AS e\n WHERE e->>'git_repo_resource_path' = $2\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "81e7f902c40d72937b494cd9f3f8c9fe4348d179add37361353fc9a02d96f2fa" +} diff --git a/backend/.sqlx/query-8d9f10a4fed1f8422224d26a4e3b1eed51f6ad271f4c76d5b430690e0ae73c1b.json b/backend/.sqlx/query-8d9f10a4fed1f8422224d26a4e3b1eed51f6ad271f4c76d5b430690e0ae73c1b.json new file mode 100644 index 0000000000..f522f8c69f --- /dev/null +++ b/backend/.sqlx/query-8d9f10a4fed1f8422224d26a4e3b1eed51f6ad271f4c76d5b430690e0ae73c1b.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value FROM variable WHERE path = $1 AND workspace_id = $2 FOR UPDATE", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "8d9f10a4fed1f8422224d26a4e3b1eed51f6ad271f4c76d5b430690e0ae73c1b" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index c43b4b308d..0641bed8dd 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -b342a1bdeda7ebba2da64c71ced6ef5dfac48663 +7d11df2f77c8af2da18269532fe276b307dd2a54 diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 396ab371e3..ea3c99baba 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -353,9 +353,7 @@ pub async fn initial_load( ) } }); - pass.action(windmill_common::min_version::store_min_keep_alive_version( - db, - )); + pass.action(windmill_common::min_version::store_min_keep_alive_version(db)); pass.setting( windmill_common::global_settings::INSTANCE_EVENTS_WEBHOOK_SETTING, false, @@ -701,6 +699,7 @@ pub async fn initial_load( pass.run(conn).await; } + pub fn apply_metrics_enabled(value: Option) { if let Some(serde_json::Value::Bool(t)) = value { METRICS_ENABLED.store(t, Ordering::Relaxed) @@ -1057,8 +1056,8 @@ pub fn apply_fork_workspace_tag_append_fork_suffix(value: Option error::Result<()> { - let v = load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true) - .await?; + let v = + load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true).await?; apply_critical_alert_mute_ui_setting(v); Ok(()) } @@ -2657,6 +2656,7 @@ pub async fn reload_timeout_wait_result_setting(conn: &Connection) { .await; } + pub async fn reload_extra_pip_index_url_setting(conn: &Connection) { reload_option_setting_with_tracing( conn, @@ -2747,6 +2747,7 @@ pub async fn reload_bunfig_install_scopes_setting(conn: &Connection) { .await; } + pub async fn reload_nuget_config_setting(conn: &Connection) { reload_option_setting_with_tracing( conn, @@ -2854,6 +2855,7 @@ pub async fn reload_ruby_repos_setting(conn: &Connection) { .await; } + pub async fn reload_workspace_registries_setting(conn: &Connection) { match load_value_from_global_settings_with_conn( conn, @@ -3092,6 +3094,7 @@ pub async fn apply_job_isolation_setting(value: Option) { } } + async fn resolve_license_key_value(conn: &Connection, quiet: bool) -> anyhow::Result { let q = load_value_from_global_settings_with_conn(conn, LICENSE_KEY_SETTING, true) .await @@ -3386,10 +3389,7 @@ impl<'a> SettingsPass<'a> { // on compile-time defaults until the next full reload. Only the single-query transport // can fail this way; over HTTP the batch already is the per-setting read. if matches!(conn, Connection::Sql(_)) && values.is_empty() && !names.is_empty() { - tracing::warn!( - "Falling back to per-setting reads for {} settings", - names.len() - ); + tracing::warn!("Falling back to per-setting reads for {} settings", names.len()); values = fetch_settings_individually(conn, &names).await; } for (name, http) in &declared { @@ -3781,6 +3781,7 @@ pub fn parse_setting_value( value } + #[cfg(feature = "prometheus")] pub async fn monitor_pool(db: &DB) { if METRICS_ENABLED.load(Ordering::Relaxed) { @@ -4717,6 +4718,7 @@ async fn maintain_git_credentials_inner(db: &Pool) -> error::Result<() Ok(()) } +#[cfg(feature = "private")] #[cfg(feature = "private")] async fn poll_git_auto_pull_inner(db: &Pool) -> error::Result<()> { use windmill_common::workspaces::{AutoPullMode, WorkspaceGitSyncSettings}; @@ -6614,6 +6616,7 @@ pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<( Ok(()) } + pub async fn reload_jwt_secret_setting(db: &DB) -> error::Result<()> { let v = load_value_from_global_settings(db, JWT_SECRET_SETTING).await?; apply_jwt_secret_setting(db, v).await diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index 1cf4edb170..4890122300 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -3937,6 +3937,7 @@ async fn edit_git_sync_config( clear_client_supplied_auto_pull_state(ap); } repo.open_pr_error = None; + repo.credential = None; } reject_parent_only_git_sync_settings_on_fork( &db, @@ -4037,6 +4038,7 @@ async fn edit_git_sync_config( continue; }; repo.open_pr_error = old.open_pr_error.clone(); + repo.credential = old.credential.clone(); if let (Some(new_ap), Some(old_ap)) = (repo.auto_pull.as_mut(), old.auto_pull.as_ref()) { @@ -4180,6 +4182,7 @@ async fn edit_git_sync_repository( clear_client_supplied_auto_pull_state(ap); } new_config.repository.open_pr_error = None; + new_config.repository.credential = None; reject_parent_only_git_sync_settings_on_fork( &db, &w_id, @@ -4304,6 +4307,7 @@ async fn edit_git_sync_repository( // from the UI cannot revert what the poller/webhook layer wrote. let mut updated = new_config.repository; updated.open_pr_error = existing_repo.open_pr_error.clone(); + updated.credential = existing_repo.credential.clone(); match (updated.auto_pull.as_mut(), existing_repo.auto_pull.as_ref()) { (Some(new_ap), Some(old_ap)) => { new_ap.last_synced_sha = old_ap.last_synced_sha.clone(); @@ -6181,6 +6185,7 @@ async fn update_workspace_settings( r.auto_pull = None; r.fork_open_prs = false; r.open_pr_error = None; + r.credential = None; r }) .collect(); @@ -8172,6 +8177,7 @@ async fn attach_dev_workspace( r.auto_pull = None; r.fork_open_prs = false; r.open_pr_error = None; + r.credential = None; } let serialized = serde_json::to_value(&settings).map_err(|e| Error::internal_err(e.to_string()))?; diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index d770d8ce1b..ca6a652a33 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -155,13 +155,20 @@ const MS_PER_DAY = 86_400_000 /** - * Days until the repository's own token expires, or undefined when it never - * expires and when nothing has checked it yet. + * Whole days until the repository's own token expires, or undefined when it + * never expires and when nothing has checked it yet. + * + * Counted between calendar dates, not instants: GitLab expires a token on a + * date, so measuring from "now" would call a token expiring later today + * expired, and one expiring tomorrow today's problem. */ const credentialDaysLeft = $derived.by(() => { const expiresAt = repo.credential?.expires_at if (!expiresAt) return undefined - return Math.floor((new Date(`${expiresAt}T00:00:00Z`).getTime() - Date.now()) / MS_PER_DAY) + const expiry = new Date(`${expiresAt}T00:00:00Z`).getTime() + const now = new Date() + const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + return Math.round((expiry - today) / MS_PER_DAY) }) /** @@ -583,9 +590,12 @@
{#if credentialDaysLeft === undefined} Repository token does not expire. - {:else} + {:else if repo.credential.rotatable} Repository token expires on {repo.credential.expires_at}, and Windmill renews it automatically. + {:else} + Repository token expires on {repo.credential.expires_at}. Give it the api or self_rotate + scope to let Windmill renew it automatically. {/if}
{/if}