From e29a0dc5dfed4bb62b894e059c9506873d32a62c Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Tue, 13 Oct 2026 11:52:25 -0400 Subject: [PATCH 1/8] fix: serve s3 proxy objects with nosniff and a sandbox csp (#11731) * fix: serve s3 proxy objects with nosniff and a sandbox csp Co-Authored-By: Claude Opus 5.5 (1M context) * test: assert s3 proxy error responses carry nosniff and the sandbox csp Co-Authored-By: Claude Opus 5.5 (1M context) * chore: update ee-repo-ref to 196abc042c691ea16280ee10b89fb3b2fd527236 This commit updates the EE repository reference after PR #876 was merged in windmill-ee-private. Previous ee-repo-ref: 25feaf41aa84d47f8e4adb4def2576b443a66374 New ee-repo-ref: 196abc042c691ea16280ee10b89fb3b2fd527236 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5.5 (1M context) Co-authored-by: windmill-internal-app[bot] --- backend/ee-repo-ref.txt | 2 +- backend/tests/s3_proxy_headers.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 backend/tests/s3_proxy_headers.rs diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 408bf517b1..bbd2895dd5 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -118785eaa78675089b9a676d24e5d0a2e03f3d9a +196abc042c691ea16280ee10b89fb3b2fd527236 diff --git a/backend/tests/s3_proxy_headers.rs b/backend/tests/s3_proxy_headers.rs new file mode 100644 index 0000000000..8f43f8c761 --- /dev/null +++ b/backend/tests/s3_proxy_headers.rs @@ -0,0 +1,29 @@ +//! Every s3_proxy response carries `nosniff` and the sandbox CSP, error responses included, +//! since the presigned route serves workspace objects from the app origin without a login. +#![cfg(all(feature = "enterprise", feature = "private", feature = "parquet"))] + +use sqlx::{Pool, Postgres}; +use windmill_test_utils::*; + +#[sqlx::test(fixtures("base"))] +async fn test_s3_proxy_sets_content_security_headers(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + let resp = reqwest::Client::new() + .get(format!( + "http://localhost:{port}/api/w/test-workspace/s3_proxy/_default_/x.html?exp=9999999999&sig=00" + )) + .send() + .await?; + + assert!(resp.status().is_client_error(), "status: {}", resp.status()); + assert_eq!(resp.headers()["x-content-type-options"], "nosniff"); + assert_eq!( + resp.headers()["content-security-policy"], + "sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads" + ); + Ok(()) +} From 89e6bdbcf50e065f6906e1ab93b3c4109aba3ab6 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 13 Oct 2026 18:04:43 +0200 Subject: [PATCH 2/8] fix: widen variable.value without breaking live servers' cached queries (#11737) * fix: widen variable.value only once every process can recover from it Co-Authored-By: Claude Opus 5.5 * fix: wait out the previous release's servers before widening variable.value Co-Authored-By: Claude Opus 5.5 * fix: write a v2 secret too long for the narrow column in cbc until it is widened Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- backend/src/db_connect.rs | 3 + backend/src/monitor.rs | 8 +- backend/windmill-api/src/db.rs | 6 ++ backend/windmill-common/src/db.rs | 42 ++++++++++ backend/windmill-common/src/error.rs | 4 + backend/windmill-common/src/min_version.rs | 2 + backend/windmill-common/src/variables.rs | 77 ++++++++++++++++++- .../windmill-common/src/workspace_crypt.rs | 31 +++++++- 8 files changed, 166 insertions(+), 7 deletions(-) diff --git a/backend/src/db_connect.rs b/backend/src/db_connect.rs index d4dee19f82..ab12ae6f6e 100644 --- a/backend/src/db_connect.rs +++ b/backend/src/db_connect.rs @@ -153,6 +153,9 @@ pub async fn connect( pool_options = pool_options.idle_timeout(Duration::from_secs(60)); } pool_options + .before_acquire(|_, meta| { + Box::pin(async move { Ok(!windmill_common::db::connection_is_retired(meta.age)) }) + }) .after_connect(move |conn, _| { if worker_mode { Box::pin(async move { diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 23ab2eaed1..4fae6f34ab 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -4469,12 +4469,14 @@ pub async fn monitor_db( // Re-encrypt the legacy-format secret variables once new values are written in AES-256-GCM. // Every ~5 min until one pass has gone through every workspace; spawned so a large instance - // does not hold up the join below. + // does not hold up the join below. The column widening it starts with is tried every + // iteration: a v2 value too long for the narrow column fails to save until it is done. let legacy_secret_upgrade_f = async { if server_mode && iteration.is_some() - && iteration.as_ref().unwrap().should_run(30) - && windmill_common::variables::legacy_secret_upgrade_pending() + && (windmill_common::variables::variable_value_widen_pending() + || (iteration.as_ref().unwrap().should_run(30) + && windmill_common::variables::legacy_secret_upgrade_pending())) { if let Some(db) = conn.as_sql() { let db = db.clone(); diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index 21b738891e..db99a07c92 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -113,6 +113,12 @@ lazy_static::lazy_static! { (20260909163047, include_str!( "../../migrations/20260909163047_workspace_delete_cascade_indexes.up.sql" ).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY").replace("DROP INDEX", "DROP INDEX CONCURRENTLY")), + // The widening fails every query the previous release's still-running + // servers prepared on the column; `variables::widen_variable_value` does it + // once they are gone. + (20261012213357, include_str!( + "../../migrations/20261012213357_widen_variable_value.up.sql" + ).replace("ALTER TABLE variable ALTER COLUMN value TYPE VARCHAR(15040);", "")), ].into_iter().collect(); } diff --git a/backend/windmill-common/src/db.rs b/backend/windmill-common/src/db.rs index 0048abf438..38b89ad3b6 100644 --- a/backend/windmill-common/src/db.rs +++ b/backend/windmill-common/src/db.rs @@ -290,3 +290,45 @@ impl<'b> DbExecutor<'b> for &'b mut PgConnection { &mut **self } } + +lazy_static::lazy_static! { + static ref PROCESS_START: std::time::Instant = std::time::Instant::now(); +} + +/// Milliseconds after [`PROCESS_START`] before which every pooled connection is retired; +/// 0 retires none. +static RETIRE_CONNECTIONS_OPENED_BEFORE_MS: std::sync::atomic::AtomicU64 = + std::sync::atomic::AtomicU64::new(0); + +/// Postgres refuses a statement prepared before its result columns changed type (even a +/// varchar length) with this error, on every execution, for as long as the connection lives. +/// The routine is matched as well as the message, which `lc_messages` may translate. +pub fn is_stale_cached_plan(e: &sqlx::Error) -> bool { + let sqlx::Error::Database(d) = e else { + return false; + }; + d.code().as_deref() == Some("0A000") + && (d + .try_downcast_ref::() + .is_some_and(|pg| pg.routine() == Some("RevalidateCachedQuery")) + || d.message() + .contains("cached plan must not change result type")) +} + +/// Retire every pooled connection opened up to now, so a schema change that poisoned their +/// prepared statements costs one failed query instead of failing until they expire. +pub fn retire_connections_opened_before_now() { + let now = PROCESS_START.elapsed().as_millis() as u64 + 1; + RETIRE_CONNECTIONS_OPENED_BEFORE_MS.fetch_max(now, std::sync::atomic::Ordering::Relaxed); +} + +/// For the pool's `before_acquire`: whether a connection of this age must be closed. +pub fn connection_is_retired(age: std::time::Duration) -> bool { + let cutoff = RETIRE_CONNECTIONS_OPENED_BEFORE_MS.load(std::sync::atomic::Ordering::Relaxed); + cutoff != 0 && (PROCESS_START.elapsed().saturating_sub(age).as_millis() as u64) < cutoff +} + +/// Time since this process first used its database pool. +pub fn process_uptime() -> std::time::Duration { + PROCESS_START.elapsed() +} diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index c06defaab5..a54e9eb399 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -153,6 +153,10 @@ impl From for Error { impl From for Error { #[track_caller] fn from(e: sqlx::Error) -> Self { + if crate::db::is_stale_cached_plan(&e) { + tracing::warn!("a schema change invalidated cached statements, reopening the database connections: {e}"); + crate::db::retire_connections_opened_before_now(); + } Self::SqlErr { error: e, location: prettify_location(std::panic::Location::caller()) } } } diff --git a/backend/windmill-common/src/min_version.rs b/backend/windmill-common/src/min_version.rs index 05b664b23d..70878e3713 100644 --- a/backend/windmill-common/src/min_version.rs +++ b/backend/windmill-common/src/min_version.rs @@ -27,6 +27,8 @@ pub const MIN_VERSION_SUPPORTS_JOB_TOKEN_SCOPES: VC = vc(1, 822, 0, "Restricted // stored secret variables. Reading it ships one release earlier (1.831), so a rollback to // that release still reads every secret; below it, secrets written after the switch stay // unreadable until rolled forward. Only worker versions are seen here, not servers. +// Also gates widening `variable.value`, which needs every process to retire the connections +// the widening poisons (`db::retire_connections_opened_before_now`): that also ships in 1.832. pub const MIN_VERSION_SUPPORTS_AEAD_SECRETS: VC = vc(1, 832, 0, "AES-GCM secret encryption"); pub const MIN_VERSION_SUPPORTS_NODE_DEBOUNCING: VC = vc(1, 658, 0, "Flow node debouncing"); pub const MIN_VERSION_SUPPORTS_TOKEN_HASH: VC = vc(1, 659, 0, "Token hash storage"); diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 58766cf25b..f357298243 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -400,7 +400,66 @@ pub fn legacy_secret_upgrade_pending() -> bool { && !LEGACY_SECRET_UPGRADE_DONE.load(Ordering::Relaxed) } -/// Re-encrypt the secret variables still in the legacy format in v2, workspace by workspace. +/// AES-256-GCM ciphertext is 32 bytes (magic, nonce, tag) longer than legacy CBC before base64, +/// so the longest CBC value that fits 15000 chars is exactly 15040 chars once re-encrypted. +const VARIABLE_VALUE_WIDTH: i32 = 15040; +static VARIABLE_VALUE_WIDENED: AtomicBool = AtomicBool::new(false); + +/// Whether this process has seen `variable.value` wide enough for every v2 value. Only servers +/// look, so it stays false in a worker. +pub fn variable_value_widened() -> bool { + VARIABLE_VALUE_WIDENED.load(Ordering::Relaxed) +} + +/// Whether `variable.value` may still have to be widened to fit v2 values. +pub fn variable_value_widen_pending() -> bool { + CipherFormat::current() == CipherFormat::V2 && !VARIABLE_VALUE_WIDENED.load(Ordering::Relaxed) +} + +/// Servers report no version, so the worker version gate alone cannot see a server of the +/// previous release; its rollout is assumed done once this server has been up this long. +const VARIABLE_VALUE_WIDEN_MIN_UPTIME: std::time::Duration = + std::time::Duration::from_secs(30 * 60); + +/// Widen `variable.value` to [`VARIABLE_VALUE_WIDTH`], or `false` when it is too early to. +/// Not a migration: changing a column's type, even its length, fails every statement a live +/// process prepared on it until its connection closes. It waits for v2 writes, gated on a +/// worker version that retires such connections on the first failure, so that no process too +/// old to recover is still running. +async fn widen_variable_value(db: &DB) -> error::Result { + if VARIABLE_VALUE_WIDENED.load(Ordering::Relaxed) { + return Ok(true); + } + // varchar's typmod is its length + 4, -1 when unbounded. + let typmod: i32 = sqlx::query_scalar( + "SELECT atttypmod FROM pg_attribute WHERE attrelid = 'variable'::regclass AND attname = 'value'", + ) + .fetch_one(db) + .await?; + if typmod >= 0 && typmod - 4 < VARIABLE_VALUE_WIDTH { + if crate::db::process_uptime() < VARIABLE_VALUE_WIDEN_MIN_UPTIME { + return Ok(false); + } + tracing::info!("widening variable.value to VARCHAR({VARIABLE_VALUE_WIDTH})"); + let mut tx = db.begin().await?; + // Queued behind a long transaction, the ALTER would block every variable read meanwhile. + sqlx::query("SET LOCAL lock_timeout = '3s'") + .execute(&mut *tx) + .await?; + sqlx::query(&format!( + "ALTER TABLE variable ALTER COLUMN value TYPE VARCHAR({VARIABLE_VALUE_WIDTH})" + )) + .execute(&mut *tx) + .await?; + tx.commit().await?; + crate::db::retire_connections_opened_before_now(); + } + VARIABLE_VALUE_WIDENED.store(true, Ordering::Relaxed); + Ok(true) +} + +/// Widen `variable.value` for v2 values, then re-encrypt the secret variables still in the +/// legacy format in v2, workspace by workspace. /// Idempotent, and emits no git-sync event: the plaintexts do not change. pub async fn upgrade_legacy_secret_variables(db: &DB) { struct Running; @@ -409,14 +468,26 @@ pub async fn upgrade_legacy_secret_variables(db: &DB) { LEGACY_SECRET_UPGRADE_RUNNING.store(false, Ordering::Release); } } - if !legacy_secret_upgrade_pending() + if !(variable_value_widen_pending() || legacy_secret_upgrade_pending()) || LEGACY_SECRET_UPGRADE_RUNNING.swap(true, Ordering::AcqRel) { return; } // Cleared on drop, so a pass that panics does not stop every later one in this process. let _running = Running; - upgrade_legacy_secret_variables_pass(db).await; + match widen_variable_value(db).await { + Ok(true) => {} + Ok(false) => return, + Err(e) => { + tracing::warn!( + "secret encryption upgrade: widening variable.value failed, retrying later: {e:#}" + ); + return; + } + } + if legacy_secret_upgrade_pending() { + upgrade_legacy_secret_variables_pass(db).await; + } } async fn upgrade_legacy_secret_variables_pass(db: &DB) { diff --git a/backend/windmill-common/src/workspace_crypt.rs b/backend/windmill-common/src/workspace_crypt.rs index a561060e32..26f38e123b 100644 --- a/backend/windmill-common/src/workspace_crypt.rs +++ b/backend/windmill-common/src/workspace_crypt.rs @@ -30,6 +30,7 @@ pub const V2_MAGIC: [u8; 4] = *b"wmA2"; const NONCE_LEN: usize = 12; const TAG_LEN: usize = 16; const BLOCK: usize = 16; +const NARROW_VARIABLE_VALUE_WIDTH: usize = 15000; const AEAD_KEY_INFO: &[u8] = b"windmill workspace secret aes-256-gcm v1"; @@ -102,7 +103,23 @@ impl WorkspaceCrypt { } pub fn encrypt_str(&self, plaintext: &str) -> String { - self.encrypt_str_in(plaintext, CipherFormat::current()) + self.encrypt_str_fitting( + plaintext, + CipherFormat::current(), + crate::variables::variable_value_widened(), + ) + } + + /// A v2 value is up to 44 chars longer than CBC: until `variable.value` is known widened + /// for it, one past the narrow width is written in CBC, which fits. + fn encrypt_str_fitting(&self, plaintext: &str, format: CipherFormat, widened: bool) -> String { + if format == CipherFormat::V2 { + let v2 = self.encrypt_str_in(plaintext, CipherFormat::V2); + if widened || v2.len() <= NARROW_VARIABLE_VALUE_WIDTH { + return v2; + } + } + self.encrypt_str_in(plaintext, CipherFormat::LegacyCbc) } pub fn encrypt_str_in(&self, plaintext: &str, format: CipherFormat) -> String { @@ -242,6 +259,18 @@ mod tests { ); } + #[test] + fn v2_too_long_for_a_narrow_column_is_written_in_cbc() { + let wc = WorkspaceCrypt::new(KEY); + // The longest plaintext whose CBC value fits 15000 chars. + let long = "x".repeat(11231); + let narrow = wc.encrypt_str_fitting(&long, CipherFormat::V2, false); + assert!(!wc.is_v2(&narrow) && narrow.len() <= NARROW_VARIABLE_VALUE_WIDTH); + assert_eq!(wc.decrypt_str(&narrow).unwrap(), long); + assert!(wc.is_v2(&wc.encrypt_str_fitting(&long, CipherFormat::V2, true))); + assert!(wc.is_v2(&wc.encrypt_str_fitting("short", CipherFormat::V2, false))); + } + #[test] fn v2_passes_the_block_multiple_ciphertext_shape_check() { let wc = WorkspaceCrypt::new(KEY); From c523c4f6508c314bcc5dc912fe30594ceb7fd893 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 13 Oct 2026 18:10:07 +0200 Subject: [PATCH 3/8] chore(main): release 1.832.0 (#11729) * chore(main): release 1.832.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++ backend/Cargo.lock | 158 +++++++++--------- backend/Cargo.toml | 4 +- .../parsers/windmill-parser-wasm/Cargo.lock | 48 +++--- .../parsers/windmill-parser-wasm/Cargo.toml | 2 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- chat-sdk/package-lock.json | 4 +- chat-sdk/package.json | 2 +- cli/src/core/constants.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 2 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- windmill-yaml-validator/package-lock.json | 4 +- windmill-yaml-validator/package.json | 2 +- 22 files changed, 140 insertions(+), 126 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0b517c26f7..a6316f6ef6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.831.0" + ".": "1.832.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 98686e7917..67733a3aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.832.0](https://github.com/windmill-labs/windmill/compare/v1.831.0...v1.832.0) (2026-10-13) + + +### Features + +* record each instance's python version and start new ones on 3.13 ([#11656](https://github.com/windmill-labs/windmill/issues/11656)) ([7558ce2](https://github.com/windmill-labs/windmill/commit/7558ce27a9736bef1a9b9db9b0799150dccb11a5)) + + +### Bug Fixes + +* badge the unavailable sign-in option in the oauth resource form ([#11728](https://github.com/windmill-labs/windmill/issues/11728)) ([9c44f5a](https://github.com/windmill-labs/windmill/commit/9c44f5a917fc628e870073c126bf9480c98c7f73)) +* serve s3 proxy objects with nosniff and a sandbox csp ([#11731](https://github.com/windmill-labs/windmill/issues/11731)) ([e29a0dc](https://github.com/windmill-labs/windmill/commit/e29a0dc5dfed4bb62b894e059c9506873d32a62c)) +* widen variable.value without breaking live servers' cached queries ([#11737](https://github.com/windmill-labs/windmill/issues/11737)) ([89e6bdb](https://github.com/windmill-labs/windmill/commit/89e6bdbcf50e065f6906e1ab93b3c4109aba3ab6)) + ## [1.831.0](https://github.com/windmill-labs/windmill/compare/v1.830.0...v1.831.0) (2026-10-13) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9ee5635c37..164a72d9ae 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15486,7 +15486,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-nats", @@ -15576,7 +15576,7 @@ dependencies = [ [[package]] name = "windmill-ai" -version = "1.831.0" +version = "1.832.0" dependencies = [ "async-stream", "async-trait", @@ -15611,7 +15611,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15624,7 +15624,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "argon2", @@ -15767,7 +15767,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15790,7 +15790,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15807,7 +15807,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -15833,7 +15833,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.831.0" +version = "1.832.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -15843,7 +15843,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15860,7 +15860,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "base64 0.22.1", @@ -15882,7 +15882,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -15905,7 +15905,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15921,7 +15921,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15943,7 +15943,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15965,7 +15965,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -15979,7 +15979,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-nats", @@ -16015,7 +16015,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -16041,7 +16041,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -16069,7 +16069,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -16091,7 +16091,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -16111,7 +16111,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -16153,7 +16153,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -16182,7 +16182,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.831.0" +version = "1.832.0" dependencies = [ "lazy_static", "serde", @@ -16194,7 +16194,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.831.0" +version = "1.832.0" dependencies = [ "argon2", "axum", @@ -16218,7 +16218,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -16232,7 +16232,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.831.0" +version = "1.832.0" dependencies = [ "axum", "chrono", @@ -16271,7 +16271,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.831.0" +version = "1.832.0" dependencies = [ "chrono", "lazy_static", @@ -16286,7 +16286,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "axum", @@ -16305,7 +16305,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.831.0" +version = "1.832.0" dependencies = [ "aes-gcm", "aho-corasick", @@ -16421,7 +16421,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.831.0" +version = "1.832.0" dependencies = [ "chrono", "futures", @@ -16441,7 +16441,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.831.0" +version = "1.832.0" dependencies = [ "regex", "serde", @@ -16458,7 +16458,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16485,7 +16485,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "futures", @@ -16502,7 +16502,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.831.0" +version = "1.832.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16518,7 +16518,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -16540,7 +16540,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -16571,7 +16571,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "arc-swap", @@ -16596,7 +16596,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-stream", @@ -16632,7 +16632,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "futures", @@ -16651,7 +16651,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.831.0" +version = "1.832.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16660,7 +16660,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16672,7 +16672,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -16684,7 +16684,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "gosyn", @@ -16696,7 +16696,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16708,7 +16708,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -16720,7 +16720,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "nu-parser", @@ -16731,7 +16731,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16742,7 +16742,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16754,7 +16754,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "ruff_python_ast", @@ -16765,7 +16765,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-recursion", @@ -16788,7 +16788,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -16800,7 +16800,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16814,7 +16814,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16831,7 +16831,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16844,7 +16844,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde", @@ -16856,7 +16856,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16874,7 +16874,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -16890,7 +16890,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "ruff_python_ast", @@ -16907,7 +16907,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -16921,7 +16921,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-recursion", @@ -16960,7 +16960,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "const_format", @@ -16998,7 +16998,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.831.0" +version = "1.832.0" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17009,7 +17009,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-recursion", @@ -17044,7 +17044,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17068,7 +17068,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17101,7 +17101,7 @@ dependencies = [ [[package]] name = "windmill-trigger-amqp" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17128,7 +17128,7 @@ dependencies = [ [[package]] name = "windmill-trigger-azure" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17161,7 +17161,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17181,7 +17181,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17215,7 +17215,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17252,7 +17252,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17275,7 +17275,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17299,7 +17299,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-nats", @@ -17323,7 +17323,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17358,7 +17358,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17386,7 +17386,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-trait", @@ -17411,7 +17411,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "bitflags 2.13.2", @@ -17430,7 +17430,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-once-cell", @@ -17549,7 +17549,7 @@ dependencies = [ [[package]] name = "windmill-worker-volumes" -version = "1.831.0" +version = "1.832.0" dependencies = [ "bytes", "futures", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index a72480cab2..5b9ff24831 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.831.0" +version = "1.832.0" authors.workspace = true edition.workspace = true @@ -88,7 +88,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"] [workspace.package] -version = "1.831.0" +version = "1.832.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/parsers/windmill-parser-wasm/Cargo.lock b/backend/parsers/windmill-parser-wasm/Cargo.lock index c0d6aa95d4..f8d4ae6c0d 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.lock +++ b/backend/parsers/windmill-parser-wasm/Cargo.lock @@ -6340,7 +6340,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill-common" -version = "1.831.0" +version = "1.832.0" dependencies = [ "aes-gcm", "aho-corasick", @@ -6430,7 +6430,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.831.0" +version = "1.832.0" dependencies = [ "proc-macro2", "quote", @@ -6442,7 +6442,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.831.0" +version = "1.832.0" dependencies = [ "convert_case", "serde", @@ -6451,7 +6451,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -6475,7 +6475,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "gosyn", @@ -6487,7 +6487,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6499,7 +6499,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -6511,7 +6511,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "nu-parser", @@ -6522,7 +6522,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6533,7 +6533,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6545,7 +6545,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "ruff_python_ast", @@ -6556,7 +6556,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "async-recursion", @@ -6579,7 +6579,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde_json", @@ -6591,7 +6591,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6605,7 +6605,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "convert_case", @@ -6622,7 +6622,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6635,7 +6635,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde", @@ -6647,7 +6647,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6665,7 +6665,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -6681,7 +6681,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "ruff_python_ast", @@ -6698,7 +6698,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "getrandom 0.2.17", @@ -6730,7 +6730,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "lazy_static", @@ -6744,7 +6744,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.831.0" +version = "1.832.0" dependencies = [ "anyhow", "bitflags", diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index d72119afd4..d72c4d87d0 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" members = ["."] [workspace.package] -version = "1.831.0" +version = "1.832.0" edition = "2021" authors = ["Ruben Fiszel "] diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 5295c5765f..3453b5e9ec 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.831.0 + version: 1.832.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 63e9a8a5d4..884f0a69b9 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.831.0"; +export const VERSION = "v1.832.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/chat-sdk/package-lock.json b/chat-sdk/package-lock.json index 91e9b9a081..b1c9148bbe 100644 --- a/chat-sdk/package-lock.json +++ b/chat-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@windmill-labs/chat-sdk", - "version": "1.831.0", + "version": "1.832.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@windmill-labs/chat-sdk", - "version": "1.831.0", + "version": "1.832.0", "license": "Apache-2.0", "devDependencies": { "@ai-sdk/react": "^4.0.102", diff --git a/chat-sdk/package.json b/chat-sdk/package.json index 296be0e680..efae991922 100644 --- a/chat-sdk/package.json +++ b/chat-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@windmill-labs/chat-sdk", "description": "Build chat interfaces on Windmill flows deployed in chat mode, from any frontend or raw app", - "version": "1.831.0", + "version": "1.832.0", "author": "Ruben Fiszel", "license": "Apache-2.0", "homepage": "https://github.com/windmill-labs/windmill/tree/main/chat-sdk#readme", diff --git a/cli/src/core/constants.ts b/cli/src/core/constants.ts index b6c029daa2..5afde16493 100644 --- a/cli/src/core/constants.ts +++ b/cli/src/core/constants.ts @@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork"; // (e.g. utils.ts) can read it without importing main.ts and creating a circular // dependency (main → workspace → utils → main) that triggers a TDZ. // Re-exported from main.ts for backwards compatibility. -export const VERSION = "1.831.0"; +export const VERSION = "1.832.0"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3b5ecd97be..c93f513b25 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@windmill-labs/components", - "version": "1.831.0", + "version": "1.832.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@windmill-labs/components", - "version": "1.831.0", + "version": "1.832.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 33c24f43bd..7a9a3f0777 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@windmill-labs/components", - "version": "1.831.0", + "version": "1.832.0", "scripts": { "dev": "vite dev", "dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev", diff --git a/lsp/Pipfile b/lsp/Pipfile index 4b782c4cf0..de0f791012 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.831.0" +wmill = ">=1.832.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 19ba842fff..e8a1c2e02e 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.831.0 + version: 1.832.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 573be0aa76..84fbf3eb14 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.831.0' + ModuleVersion = '1.832.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index dc227b7ccc..abe9debc23 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.831.0" +version = "1.832.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 3eaaece304..bb8ee9b251 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.831.0", + "version": "1.832.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts", "./wacError.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 810c0a7940..3240d1ca3f 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.831.0", + "version": "1.832.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "homepage": "https://github.com/windmill-labs/windmill/tree/main/typescript-client#readme", diff --git a/version.txt b/version.txt index 74dfad0170..cebe79ce72 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.831.0 +1.832.0 diff --git a/windmill-yaml-validator/package-lock.json b/windmill-yaml-validator/package-lock.json index 94b3c36b9a..7918120f9f 100644 --- a/windmill-yaml-validator/package-lock.json +++ b/windmill-yaml-validator/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-yaml-validator", - "version": "1.831.0", + "version": "1.832.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-yaml-validator", - "version": "1.831.0", + "version": "1.832.0", "license": "Apache 2.0", "dependencies": { "@stoplight/yaml": "^4.3.0", diff --git a/windmill-yaml-validator/package.json b/windmill-yaml-validator/package.json index 77dbdfcecf..bb5b50b497 100644 --- a/windmill-yaml-validator/package.json +++ b/windmill-yaml-validator/package.json @@ -1,6 +1,6 @@ { "name": "windmill-yaml-validator", - "version": "1.831.0", + "version": "1.832.0", "description": "YAML validator for Windmill flow, schedule, and trigger files", "main": "dist/index.js", "types": "dist/index.d.ts", From 130adfdd20af0e30d87986cf77b61023a9732186 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 13 Oct 2026 22:51:48 +0200 Subject: [PATCH 4/8] fix: show the email handler's SMTP status to non-superadmin admins (#11745) * fix: show the email handler's SMTP status to non-superadmin workspace admins Co-Authored-By: Claude Opus 5.5 * fix: keep the SMTP status check out of the not-configured warning Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- backend/windmill-api-settings/src/lib.rs | 10 ++++++++ backend/windmill-api/openapi.yaml | 18 ++++++++++++++ backend/windmill-common/src/server.rs | 24 +++++++++++++------ .../components/ErrorOrRecoveryHandler.svelte | 7 +----- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index 1359e8c142..f9ba1ff86f 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -132,6 +132,15 @@ async fn get_instance_ui( )) } +/// Whether email notifications can go out, for the workspace admins who configure email +/// handlers and cannot read `smtp_settings` itself (it holds the password). Asks the same +/// resolver the senders use, so SMTP set through `SMTP_*` env vars counts too. +async fn get_smtp_configured(Extension(db): Extension, _authed: ApiAuthed) -> JsonResult { + Ok(Json( + SMTP_ENABLED && windmill_common::server::is_smtp_configured(&db).await?, + )) +} + pub fn global_service() -> Router { #[warn(unused_mut)] let r = Router::new() @@ -152,6 +161,7 @@ pub fn global_service() -> Router { ) .route("/instance_config/yaml", get(get_instance_config_yaml)) .route("/test_smtp", post(test_email)) + .route("/smtp_configured", get(get_smtp_configured)) .route("/test_license_key", post(test_license_key)) .route("/send_stats", post(send_stats)) .route("/get_stats", get(get_stats)) diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 3453b5e9ec..2acb854832 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1971,6 +1971,24 @@ paths: application/json: schema: {} + /settings/smtp_configured: + get: + summary: whether instance SMTP is configured + description: | + Readable by any authenticated user. True when this build can send email + and SMTP is set, either in the smtp_settings instance setting or through + the SMTP_* environment variables. + operationId: getSmtpConfigured + tags: + - setting + responses: + "200": + description: whether email notifications can be sent + content: + application/json: + schema: + type: boolean + /settings/test_smtp: post: summary: test smtp diff --git a/backend/windmill-common/src/server.rs b/backend/windmill-common/src/server.rs index 19da3222e9..52ef7d79cb 100644 --- a/backend/windmill-common/src/server.rs +++ b/backend/windmill-common/src/server.rs @@ -31,9 +31,24 @@ pub async fn load_smtp_config(db: &DB) -> error::Result> { Ok(parse_smtp_config(value)) } +/// Whether SMTP is set, without the "not configured" warning: any signed-in user can ask, and +/// "no" is an ordinary answer there rather than something to log. +pub async fn is_smtp_configured(db: &DB) -> error::Result { + let value = crate::global_settings::load_value_from_global_settings(db, SMTP_SETTING).await?; + Ok(resolve_smtp_config(value).is_some()) +} + /// The half of [`load_smtp_config`] after the read, so a batched settings pass can parse a /// value it already fetched. pub fn parse_smtp_config(value: Option) -> Option { + let smtp = resolve_smtp_config(value); + if smtp.is_none() { + tracing::warn!("SMTP not configured"); + } + smtp +} + +fn resolve_smtp_config(value: Option) -> Option { let config: SmtpConfigOpt = value .map(|x| serde_json::from_value(x).ok()) .flatten() @@ -57,7 +72,7 @@ pub fn parse_smtp_config(value: Option) -> Option { } else { None }; - let smtp = config_smtp.or( + config_smtp.or( if let (Some(host), username, password) = ( std::env::var("SMTP_HOST").ok(), std::env::var("SMTP_USERNAME").ok(), @@ -86,12 +101,7 @@ pub fn parse_smtp_config(value: Option) -> Option { } else { None }, - ); - if smtp.is_none() { - tracing::warn!("SMTP not configured"); - } - - smtp + ) } impl Default for SmtpConfigOpt { diff --git a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte index c624b5a148..ef805162a6 100644 --- a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte +++ b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte @@ -73,7 +73,6 @@ import { syncSlackBotArg } from './handlerSlackArg' import SmtpConfigurationStatus from './common/smtp/SmtpConfigurationStatus.svelte' import { SettingService } from '$lib/gen' - import { isSmtpSettingsValid } from './instanceSettings/SmtpSettings.svelte' import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte' const operatingWorkspace = useOperatingWorkspace() @@ -171,11 +170,7 @@ async function loadSmtpConfiguration() { try { - const smtpSettings = (await SettingService.getGlobal({ key: 'smtp_settings' })) as Record< - string, - any - > | null - hasSmtpConfig = smtpSettings ? isSmtpSettingsValid(smtpSettings) : false + hasSmtpConfig = await SettingService.getSmtpConfigured() } catch (error) { hasSmtpConfig = false } From 9e0db2c7778d2e9e186f0d38c18674a9c373c5d1 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 13 Oct 2026 23:09:09 +0200 Subject: [PATCH 5/8] oauth: rename outlook provider to ms_outlook (#11743) * oauth: rename outlook provider to msoutlook Microsoft 365 products are ms (msteams, msoutlook). Renames the oauth_connect.json key and the icon map key. Co-Authored-By: Claude Opus 5.5 * oauth: use ms_outlook, not msoutlook Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- backend/oauth_connect.json | 2 +- frontend/src/lib/components/icons/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json index b5ebd80e2a..a29aecb92d 100644 --- a/backend/oauth_connect.json +++ b/backend/oauth_connect.json @@ -446,7 +446,7 @@ ], "req_body_auth": true }, - "outlook": { + "ms_outlook": { "req_body_auth": true, "scopes": ["offline_access", "User.Read", "Mail.ReadWrite", "Mail.Send", "Calendars.ReadWrite", "Contacts.ReadWrite"], "scope_options": [ diff --git a/frontend/src/lib/components/icons/index.ts b/frontend/src/lib/components/icons/index.ts index 2b63491c6c..70d2450f29 100644 --- a/frontend/src/lib/components/icons/index.ts +++ b/frontend/src/lib/components/icons/index.ts @@ -446,7 +446,7 @@ export const APP_TO_ICON_COMPONENT = { quickbooks: QuickbooksIcon, ms_teams_webhook: MsTeamsIcon, teams: MsTeamsIcon, - outlook: OutlookIcon, + ms_outlook: OutlookIcon, mailgun: MailgunIcon, ipinfo: IpinfoIcon, gworkspace: GoogleIcon, From 2f9a111bbb14a40e1341879e1ef63c3457e7a08d Mon Sep 17 00:00:00 2001 From: AlexRV12 <71396855+AlexRV12@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:09:59 +0200 Subject: [PATCH 6/8] fix: error handler picker keeps no per-tab state (#11742) --- .../components/ErrorOrRecoveryHandler.svelte | 252 ++++-------------- .../AssetGraph/PipelineInsertMenu.svelte | 15 +- .../src/lib/components/handlerDraft.svelte.ts | 118 ++++++++ .../src/lib/components/handlerDraft.test.ts | 67 +++++ .../lib/components/handlerSlackArg.test.ts | 21 -- .../src/lib/components/handlerSlackArg.ts | 17 -- .../TriggerRetriesAndErrorHandler.svelte | 16 +- .../amqp/AmqpTriggerEditorInner.svelte | 33 ++- .../azure/AzureTriggerEditorInner.svelte | 40 +-- .../email/EmailTriggerEditorInner.svelte | 33 ++- .../triggers/gcp/GcpTriggerEditorInner.svelte | 33 ++- .../triggers/http/RouteEditorInner.svelte | 33 ++- .../kafka/KafkaTriggerEditorInner.svelte | 34 ++- .../mqtt/MqttTriggerEditorInner.svelte | 33 ++- .../nats/NatsTriggerEditorInner.svelte | 34 ++- .../PostgresTriggerEditorInner.svelte | 33 ++- .../schedules/ScheduleAdvancedOptions.svelte | 236 ++++++---------- .../schedules/ScheduleEditorInner.svelte | 175 +++--------- .../triggers/sqs/SqsTriggerEditorInner.svelte | 33 ++- frontend/src/lib/components/triggers/utils.ts | 15 -- .../WebsocketTriggerEditorInner.svelte | 33 ++- .../(logged)/workspace_settings/+page.svelte | 175 ++++++------ 22 files changed, 662 insertions(+), 817 deletions(-) create mode 100644 frontend/src/lib/components/handlerDraft.svelte.ts create mode 100644 frontend/src/lib/components/handlerDraft.test.ts delete mode 100644 frontend/src/lib/components/handlerSlackArg.test.ts delete mode 100644 frontend/src/lib/components/handlerSlackArg.ts diff --git a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte index ef805162a6..6d95ad0c82 100644 --- a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte +++ b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte @@ -1,6 +1,4 @@
- + {#snippet children({ item })} @@ -413,21 +290,21 @@
- {#if handlerSelected === 'custom'} + {#if draft.selected === 'custom'}
- {#if !handlerPath} + {#if !draft.custom.path}
- {#if handlerPath} + {#if draft.custom.path}

Extra arguments

{#await import('$lib/components/SchemaForm.svelte')} @@ -460,7 +337,7 @@ @@ -470,7 +347,7 @@ {/if}
{/if} - {:else if handlerSelected === 'slack'} + {:else if draft.selected === 'slack'} { - if (e.detail && errorOrRecovery === 'error') { - handlerPath = slackHandlerScriptPath - } else if (e.detail && errorOrRecovery === 'recovery') { - handlerPath = slackRecoveryHandler - } else if (e.detail && errorOrRecovery === 'success') { - handlerPath = slackSuccessHandler - } else { - handlerPath = undefined - } + on:change={(e) => { + draft.slack.path = e.detail ? builtinHubPath(errorOrRecovery, 'slack') : undefined }} /> {/if} - {#if workspaceConnectedToSlack && isSlackHandler(handlerPath)} + {#if workspaceConnectedToSlack && draft.slack.path}
{#await import('$lib/components/SchemaForm.svelte')} @@ -510,18 +379,18 @@ schemaFieldTooltip={{ channel: 'Slack channel name without the "#" - example: "windmill-alerts"' }} - bind:args={handlerExtraArgs} + bind:args={draft.slack.args} shouldHideNoInputs className="text-xs" /> {/await} - {#if $enterpriseLicense && isSlackHandler(handlerPath)} + {#if $enterpriseLicense} {#if connectionTestJob !== undefined} @@ -554,7 +423,7 @@ {:else if workspaceConnectedToSlack == undefined} {/if} - {:else if handlerSelected === 'teams'} + {:else if draft.selected === 'teams'} { - if (e.detail && errorOrRecovery === 'error') { - handlerPath = teamsHandlerScriptPath - } else if (e.detail && errorOrRecovery === 'recovery') { - handlerPath = teamsRecoveryHandler - } else if (e.detail && errorOrRecovery === 'success') { - handlerPath = teamsSuccessHandler - } else { - handlerPath = undefined - } + on:change={(e) => { + draft.teams.path = e.detail ? builtinHubPath(errorOrRecovery, 'teams') : undefined }} /> {/if} @@ -599,26 +460,26 @@ placeholder="Search Teams channels" workspace={effectiveWorkspace} teamId={teams_team_guid} - selectedChannel={handlerExtraArgs['channel'] + selectedChannel={draft.teams.args['channel'] ? { - channel_id: handlerExtraArgs['channel'], - channel_name: handlerExtraArgs['channel_name'] + channel_id: draft.teams.args['channel'], + channel_name: draft.teams.args['channel_name'] } : undefined} onSelectedChannelChange={(channel) => { - handlerExtraArgs['channel'] = channel?.channel_id - handlerExtraArgs['channel_name'] = channel?.channel_name + draft.teams.args['channel'] = channel?.channel_id + draft.teams.args['channel_name'] = channel?.channel_name }} onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)} />
- {#if $enterpriseLicense && isTeamsHandler(handlerPath) && workspaceConnectedToTeams} + {#if $enterpriseLicense && draft.teams.path && workspaceConnectedToTeams} {#if connectionTestJob !== undefined} @@ -644,7 +505,7 @@ {:else if workspaceConnectedToTeams == undefined} {/if} - {:else if handlerSelected === 'email'} + {:else if draft.selected === 'email'} {#if isCloudHosted()} Email notifications for trigger failures are only available in self-hosted Windmill @@ -657,8 +518,8 @@ handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [], - (recipients) => (handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = recipients) + () => draft.email.args.email_recipients ?? [], + (recipients) => (draft.email.args.email_recipients = recipients) } placeholder="Enter email addresses..." onCreateItem={(email) => { @@ -667,23 +528,24 @@ sendUserToast('Invalid email format', true) return } - const currentArray = handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [] - handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = [...currentArray, email] + draft.email.args.email_recipients = [ + ...(draft.email.args.email_recipients ?? []), + email + ] }} class="w-full" /> - {#if handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length > 0} + {#if draft.email.args.email_recipients?.length > 0} - {handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length} email{handlerExtraArgs[ - EMAIL_RECIPIENTS_KEY - ]?.length === 1 + {draft.email.args.email_recipients.length} email{draft.email.args.email_recipients + .length === 1 ? '' : 's'} configured {/if}
{/if} - {:else if handlerSelected === 'instance_alerts'} + {:else if draft.selected === 'instance_alerts'}
Failed jobs are reported to the Slack, Teams and email channels configured at the instance diff --git a/frontend/src/lib/components/assets/AssetGraph/PipelineInsertMenu.svelte b/frontend/src/lib/components/assets/AssetGraph/PipelineInsertMenu.svelte index 56f0877ce0..c1b64b59a2 100644 --- a/frontend/src/lib/components/assets/AssetGraph/PipelineInsertMenu.svelte +++ b/frontend/src/lib/components/assets/AssetGraph/PipelineInsertMenu.svelte @@ -656,22 +656,13 @@ wsId={$workspaceStore} itemKind="script" canWrite - bind:errorHandlerSelected={config.advanced.errorHandlerSelected} - bind:errorHandlerPath={config.advanced.errorHandlerPath} - bind:errorHandleritemKind={config.advanced.errorHandleritemKind} - bind:errorHandlerExtraArgs={config.advanced.errorHandlerExtraArgs} + bind:errorHandler={config.advanced.errorHandler} bind:wsErrorHandlerMuted={config.advanced.wsErrorHandlerMuted} bind:failedTimes={config.advanced.failedTimes} bind:failedExact={config.advanced.failedExact} - bind:recoveryHandlerSelected={config.advanced.recoveryHandlerSelected} - bind:recoveryHandlerPath={config.advanced.recoveryHandlerPath} - bind:recoveryHandlerItemKind={config.advanced.recoveryHandlerItemKind} - bind:recoveryHandlerExtraArgs={config.advanced.recoveryHandlerExtraArgs} + bind:recoveryHandler={config.advanced.recoveryHandler} bind:recoveredTimes={config.advanced.recoveredTimes} - bind:successHandlerSelected={config.advanced.successHandlerSelected} - bind:successHandlerPath={config.advanced.successHandlerPath} - bind:successHandlerItemKind={config.advanced.successHandlerItemKind} - bind:successHandlerExtraArgs={config.advanced.successHandlerExtraArgs} + bind:successHandler={config.advanced.successHandler} bind:retry={config.advanced.retry} bind:dynamicSkipPath={config.advanced.dynamicSkipPath} bind:tag={config.advanced.tag} diff --git a/frontend/src/lib/components/handlerDraft.svelte.ts b/frontend/src/lib/components/handlerDraft.svelte.ts new file mode 100644 index 0000000000..a1422e42ed --- /dev/null +++ b/frontend/src/lib/components/handlerDraft.svelte.ts @@ -0,0 +1,118 @@ +import type { ErrorHandler } from '$lib/gen' +import { WORKSPACE_SLACK_BOT_TOKEN_PATH } from '$lib/consts' +import hubPaths from '$lib/hubPaths.json' + +export type HandlerMode = 'error' | 'recovery' | 'success' +type BuiltinDestination = 'slack' | 'teams' | 'email' + +export type HandlerSlot = { path?: string; args: Record } +export type HandlerDraft = { + selected: ErrorHandler + slack: HandlerSlot + teams: HandlerSlot + email: HandlerSlot + custom: HandlerSlot & { kind: 'script' | 'flow' } +} +type StoredHandler = { path?: string; kind: 'script' | 'flow'; args: Record } + +const SLACK_BOT_ARG = '$res:' + WORKSPACE_SLACK_BOT_TOKEN_PATH + +// Suffixes rather than full paths, so a handler stored under an older hub id is still recognised. +// Email uses one script for every mode. +const EMAIL_SUFFIX = '/workspace-or-error-handler-email' +const BUILTIN_SUFFIXES: Record> = { + error: { + slack: '/workspace-or-schedule-error-handler-slack', + teams: '/workspace-or-schedule-error-handler-teams' + }, + recovery: { + slack: '/schedule-recovery-handler-slack', + teams: '/schedule-recovery-handler-teams' + }, + success: { + slack: '/schedule-success-handler-slack', + teams: '/schedule-success-handler-teams' + } +} + +// The only settings each built-in hub script takes besides the ones the backend injects. +const BUILTIN_KEYS: Record = { + slack: ['channel'], + teams: ['channel', 'channel_name'], + email: ['email_recipients'] +} + +export function builtinHubPath(mode: HandlerMode, destination: 'slack' | 'teams'): string { + const key = + `${destination}${mode[0].toUpperCase()}${mode.slice(1)}Handler` as keyof typeof hubPaths + return hubPaths[key] +} + +export function handlerTypeForPath(mode: HandlerMode, path: string | undefined): ErrorHandler { + if (path?.startsWith('hub/')) { + if (path.endsWith(EMAIL_SUFFIX)) return 'email' + for (const [destination, suffix] of Object.entries(BUILTIN_SUFFIXES[mode])) { + if (path.endsWith(suffix)) return destination as BuiltinDestination + } + } + return 'custom' +} + +function emptyDraft(selected: ErrorHandler): HandlerDraft { + return { + selected, + slack: { args: {} }, + teams: { args: {} }, + email: { path: hubPaths.emailErrorHandler, args: {} }, + custom: { kind: 'script', args: {} } + } +} + +function pick(args: Record, keys: string[]): Record { + return Object.fromEntries(keys.filter((k) => args[k] !== undefined).map((k) => [k, args[k]])) +} + +/** Puts a stored handler into the slot of its destination; every other slot starts empty. */ +export function handlerDraftFromStored( + mode: HandlerMode, + path: string | undefined, + kind: 'script' | 'flow' = 'script', + args: Record | null | undefined = {}, + opts: { asCustom?: boolean; emptySelected?: ErrorHandler } = {} +): HandlerDraft { + if (!path) return emptyDraft(opts.emptySelected ?? 'custom') + const destination = opts.asCustom ? 'custom' : handlerTypeForPath(mode, path) + const draft = emptyDraft(destination) + if (destination === 'custom') { + draft.custom = { path, kind, args: { ...(args ?? {}) } } + } else if (destination !== 'instance_alerts') { + draft[destination] = { path, args: pick(args ?? {}, BUILTIN_KEYS[destination]) } + } + return draft +} + +/** What a save stores: the selected destination only. */ +export function handlerDraftToStored(draft: HandlerDraft): StoredHandler { + const none: StoredHandler = { kind: 'script', args: {} } + switch (draft.selected) { + case 'custom': { + const { path, kind, args } = draft.custom + return path ? { path, kind, args: $state.snapshot(args) } : none + } + case 'slack': + case 'teams': + case 'email': { + const { path, args } = draft[draft.selected] + if (!path) return none + const stored = { path, kind: 'script' as const, args: $state.snapshot(args) } + if (draft.selected === 'slack') stored.args.slack = SLACK_BOT_ARG + return stored + } + default: + return none + } +} + +export function cloneHandlerDraft(draft: HandlerDraft): HandlerDraft { + return $state.snapshot(draft) as HandlerDraft +} diff --git a/frontend/src/lib/components/handlerDraft.test.ts b/frontend/src/lib/components/handlerDraft.test.ts new file mode 100644 index 0000000000..ee6ec4a040 --- /dev/null +++ b/frontend/src/lib/components/handlerDraft.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { + handlerDraftFromStored, + handlerDraftToStored, + handlerTypeForPath +} from './handlerDraft.svelte' + +const SLACK_BOT = '$res:f/slack_bot/bot_token' + +describe('handler draft', () => { + it('round-trips every destination unchanged', () => { + const stored = [ + { + path: 'hub/28794/workspace-or-schedule-error-handler-slack', + kind: 'script' as const, + args: { channel: 'alerts', slack: SLACK_BOT } + }, + { + path: 'hub/19742/workspace-or-schedule-error-handler-teams', + kind: 'script' as const, + args: { channel: 'c1', channel_name: 'General' } + }, + { + path: 'hub/19795/workspace-or-error-handler-email', + kind: 'script' as const, + args: { email_recipients: ['ops@example.com'] } + }, + { + path: 'f/handlers/on_error', + kind: 'flow' as const, + args: { slack: '$res:u/me/my_slack', channel: 'own' } + } + ] + for (const s of stored) { + const draft = handlerDraftFromStored('error', s.path, s.kind, s.args) + expect(handlerDraftToStored(draft)).toEqual(s) + } + }) + + it("saves only the selected destination's settings", () => { + const draft = handlerDraftFromStored('error', 'u/me/handler', 'script', { + webhook_url: 'https://hooks.example/custom' + }) + draft.selected = 'email' + draft.email.args.email_recipients = ['ops@example.com'] + expect(handlerDraftToStored(draft)).toEqual({ + path: 'hub/19795/workspace-or-error-handler-email', + kind: 'script', + args: { email_recipients: ['ops@example.com'] } + }) + }) + + it('recognises built-in handlers per mode', () => { + const recoverySlack = 'hub/28791/slack/schedule-recovery-handler-slack' + expect(handlerTypeForPath('recovery', recoverySlack)).toBe('slack') + expect(handlerTypeForPath('error', recoverySlack)).toBe('custom') + }) + + it('stores no handler when the destination has none', () => { + const draft = handlerDraftFromStored('error', undefined) + expect(handlerDraftToStored(draft)).toEqual({ kind: 'script', args: {} }) + draft.selected = 'slack' + expect(handlerDraftToStored(draft)).toEqual({ kind: 'script', args: {} }) + draft.selected = 'instance_alerts' + expect(handlerDraftToStored(draft)).toEqual({ kind: 'script', args: {} }) + }) +}) diff --git a/frontend/src/lib/components/handlerSlackArg.test.ts b/frontend/src/lib/components/handlerSlackArg.test.ts deleted file mode 100644 index b087a55f26..0000000000 --- a/frontend/src/lib/components/handlerSlackArg.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { syncSlackBotArg } from './handlerSlackArg' - -const AUTO = '$res:f/slack_bot/bot_token' - -describe('syncSlackBotArg', () => { - it("leaves a custom handler's slack value alone", () => { - const args = { slack: '$res:u/me/my_slack' } - syncSlackBotArg(args, false, false) - syncSlackBotArg(args, false, true) - expect(args).toEqual({ slack: '$res:u/me/my_slack' }) - }) - - it('sets the bot token for the built-in and drops it when leaving', () => { - const args: Record = {} - syncSlackBotArg(args, true, false) - expect(args.slack).toBe(AUTO) - syncSlackBotArg(args, false, true) - expect(args).toStrictEqual({}) - }) -}) diff --git a/frontend/src/lib/components/handlerSlackArg.ts b/frontend/src/lib/components/handlerSlackArg.ts deleted file mode 100644 index 6b8e4d6f5c..0000000000 --- a/frontend/src/lib/components/handlerSlackArg.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { WORKSPACE_SLACK_BOT_TOKEN_PATH } from '$lib/consts' - -const AUTO_SLACK_ARG = '$res:' + WORKSPACE_SLACK_BOT_TOKEN_PATH - -// `slack` belongs to the Slack built-in only while it holds the value the picker set; any other -// value is a custom handler's own argument and must survive handler switches and reloads. -export function syncSlackBotArg( - args: Record, - isSlackBuiltin: boolean, - wasSlackBuiltin: boolean -) { - if (isSlackBuiltin) { - args.slack = AUTO_SLACK_ARG - } else if (wasSlackBuiltin && args.slack === AUTO_SLACK_ARG) { - delete args.slack - } -} diff --git a/frontend/src/lib/components/triggers/TriggerRetriesAndErrorHandler.svelte b/frontend/src/lib/components/triggers/TriggerRetriesAndErrorHandler.svelte index 86dbf31e46..f71205bc13 100644 --- a/frontend/src/lib/components/triggers/TriggerRetriesAndErrorHandler.svelte +++ b/frontend/src/lib/components/triggers/TriggerRetriesAndErrorHandler.svelte @@ -1,9 +1,10 @@ {#snippet workspaceDisc()} @@ -241,89 +264,129 @@ not this component) — so the lead can run the whole line. {/snippet} -