diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe4f159cf..474533d914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.501.3](https://github.com/windmill-labs/windmill/compare/v1.501.2...v1.501.3) (2025-06-25) + + +### Bug Fixes + +* **backend:** return correct content-type for openapi spec ([#6045](https://github.com/windmill-labs/windmill/issues/6045)) ([44457c7](https://github.com/windmill-labs/windmill/commit/44457c72cf75c969de97c39bb23f57acad268e10)) +* **frontend:** load all flow jobs on page load ([#6029](https://github.com/windmill-labs/windmill/issues/6029)) ([dc5e764](https://github.com/windmill-labs/windmill/commit/dc5e764d9db9251dc356094d6ac47c45fdf72c74)) +* ignore type only imports when computing ts lockfiles ([900c8ed](https://github.com/windmill-labs/windmill/commit/900c8edd7b35802e23a1359029da8ddbfb783753)) +* improve ordering of forms for non complete ordering + array schema fix ([18ee03a](https://github.com/windmill-labs/windmill/commit/18ee03a32371885f5e608cb306b5ccbccc31dac5)) +* missing static_asset_config from api call ([#6058](https://github.com/windmill-labs/windmill/issues/6058)) ([395f1ff](https://github.com/windmill-labs/windmill/commit/395f1ff8ba05020d72d1d8b34bd6bb32517b7aec)) + ## [1.501.2](https://github.com/windmill-labs/windmill/compare/v1.501.1...v1.501.2) (2025-06-24) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 53d9906e77..a9f7a2d86a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2364,9 +2364,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -4502,6 +4502,15 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -4519,7 +4528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -4531,10 +4540,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.60.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -4542,7 +4563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -6149,19 +6170,26 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hf-hub" -version = "0.3.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b780635574b3d92f036890d8373433d6f9fc7abb320ee42a5c25897fc8ed732" +checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" dependencies = [ - "dirs 5.0.1", + "dirs 6.0.0", + "futures", + "http 1.3.1", "indicatif", + "libc", "log", "native-tls", - "rand 0.8.5", + "num_cpus", + "rand 0.9.0", + "reqwest 0.12.20", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", + "tokio", "ureq", + "windows-sys 0.60.2", ] [[package]] @@ -7266,9 +7294,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", @@ -10103,6 +10131,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -10411,7 +10450,7 @@ dependencies = [ [[package]] name = "rmcp" version = "0.1.5" -source = "git+https://github.com/modelcontextprotocol/rust-sdk#893969ac7780897bbb804d41fb98347553386f33" +source = "git+https://github.com/modelcontextprotocol/rust-sdk#e6effb327883d0a8a91f5efbb527194747eee57b" dependencies = [ "base64 0.22.1", "bytes", @@ -10440,7 +10479,7 @@ dependencies = [ [[package]] name = "rmcp-macros" version = "0.1.5" -source = "git+https://github.com/modelcontextprotocol/rust-sdk#893969ac7780897bbb804d41fb98347553386f33" +source = "git+https://github.com/modelcontextprotocol/rust-sdk#e6effb327883d0a8a91f5efbb527194747eee57b" dependencies = [ "darling 0.20.11", "proc-macro2", @@ -11599,6 +11638,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "sourcemap" version = "8.0.1" @@ -14026,6 +14076,7 @@ dependencies = [ "rustls-pki-types", "serde", "serde_json", + "socks", "url", "webpki-roots 0.26.11", ] @@ -14565,7 +14616,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "axum", @@ -14617,7 +14668,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "argon2", @@ -14728,7 +14779,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.501.2" +version = "1.501.3" dependencies = [ "base64 0.22.1", "chrono", @@ -14743,7 +14794,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.501.2" +version = "1.501.3" dependencies = [ "chrono", "serde", @@ -14756,7 +14807,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "serde", @@ -14770,7 +14821,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "async-stream", @@ -14847,7 +14898,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.501.2" +version = "1.501.3" dependencies = [ "regex", "serde", @@ -14861,7 +14912,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "bytes", @@ -14884,7 +14935,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.501.2" +version = "1.501.3" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -14896,7 +14947,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.501.2" +version = "1.501.3" dependencies = [ "convert_case 0.6.0", "serde", @@ -14905,7 +14956,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "lazy_static", @@ -14917,7 +14968,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "serde_json", @@ -14929,7 +14980,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "gosyn", @@ -14941,7 +14992,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "lazy_static", @@ -14953,7 +15004,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "serde_json", @@ -14965,7 +15016,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "nu-parser", @@ -14976,7 +15027,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "itertools 0.14.0", @@ -14987,7 +15038,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "itertools 0.14.0", @@ -14998,7 +15049,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "async-recursion", @@ -15021,7 +15072,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15038,7 +15089,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "lazy_static", @@ -15050,7 +15101,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "lazy_static", @@ -15068,7 +15119,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15092,7 +15143,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "serde_json", @@ -15102,7 +15153,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "async-recursion", @@ -15135,7 +15186,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.501.2" +version = "1.501.3" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15145,7 +15196,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.501.2" +version = "1.501.3" dependencies = [ "anyhow", "async-recursion", @@ -15781,9 +15832,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", "rustix 1.0.7", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 9f1a354bfd..5f1b540cd3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.501.2" +version = "1.501.3" authors.workspace = true edition.workspace = true @@ -32,7 +32,7 @@ members = [ ] [workspace.package] -version = "1.501.2" +version = "1.501.3" authors = ["Ruben Fiszel "] edition = "2021" @@ -329,7 +329,7 @@ jsonwebtoken = "8.3.0" pem = "3.0.1" nix = { version = "0.27.1", features = ["process", "signal"] } tinyvector = { git = "https://github.com/windmill-labs/tinyvector", rev = "20823b94c20f2b9093f318badd24026cf54dcc85" } -hf-hub = "0.3.2" +hf-hub = "0.4.3" tokenizers = "0.14.1" candle-core = "0.9.1" candle-transformers = "0.9.1" diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 2024f4880c..8ace23a1d3 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -29,12 +29,36 @@ use wasm_bindgen::prelude::*; struct ImportsFinder { imports: HashSet, + skip_type_only: bool, } impl Visit for ImportsFinder { noop_visit_type!(); fn visit_import_decl(&mut self, n: &swc_ecma_ast::ImportDecl) { + if self.skip_type_only { + if n.type_only { + return; + } + if n.specifiers.len() > 0 { + let mut is_type_only = true; + + for specifier in n.specifiers.iter() { + match specifier { + swc_ecma_ast::ImportSpecifier::Named( + swc_ecma_ast::ImportNamedSpecifier { is_type_only, .. }, + ) if *is_type_only => (), + _ => { + is_type_only = false; + break; + } + } + } + if is_type_only { + return; + } + } + } if let Some(ref s) = n.src.raw { let s = s.to_string(); if s.starts_with("'") && s.ends_with("'") { @@ -46,7 +70,7 @@ impl Visit for ImportsFinder { } } -pub fn parse_expr_for_imports(code: &str) -> anyhow::Result> { +pub fn parse_expr_for_imports(code: &str, skip_type_only: bool) -> anyhow::Result> { let cm: Lrc = Default::default(); let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into()); let mut tss = TsSyntax::default(); @@ -72,7 +96,7 @@ pub fn parse_expr_for_imports(code: &str) -> anyhow::Result> { anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}") })?; - let mut visitor = ImportsFinder { imports: HashSet::new() }; + let mut visitor = ImportsFinder { imports: HashSet::new(), skip_type_only }; visitor.visit_module(&expr); let mut imports: Vec<_> = visitor.imports.into_iter().collect(); @@ -318,7 +342,7 @@ lazy_static::lazy_static! { } pub fn remove_pinned_imports(code: &str) -> anyhow::Result { - let mut imports = parse_expr_for_imports(code)?; + let mut imports = parse_expr_for_imports(code, false)?; imports.sort_by_key(|f| 0 - (f.len() as i32)); let mut content = code.to_string(); for import in imports { diff --git a/backend/parsers/windmill-parser-ts/tests/tests.rs b/backend/parsers/windmill-parser-ts/tests/tests.rs new file mode 100644 index 0000000000..f0ffc2d7c2 --- /dev/null +++ b/backend/parsers/windmill-parser-ts/tests/tests.rs @@ -0,0 +1,19 @@ +mod tests { + use windmill_parser_ts::parse_expr_for_imports; + + #[test] + fn test_imports() { + let code = r#" + import { foo } from "bar"; + import type { foo } from "bar2"; + import { type foo, bar } from "bar3"; + import { bar, type foo } from "bar7"; + + import { type foo, type bar } from "bar4"; + import * as foo from "bar5"; + import foo from "bar6"; + "#; + let imports = parse_expr_for_imports(code, true).unwrap(); + assert_eq!(imports, vec!["bar", "bar3", "bar5", "bar6", "bar7"]); + } +} diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index 891a7bd3f1..dd890ea946 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -41,7 +41,7 @@ pub fn parse_outputs(code: &str) -> String { #[cfg(feature = "ts-parser")] #[wasm_bindgen] pub fn parse_ts_imports(code: &str) -> String { - let parsed = parse_expr_for_imports(code); + let parsed = parse_expr_for_imports(code, false); let r = if let Ok(parsed) = parsed { json!({ "imports": parsed }) } else { diff --git a/backend/parsers/windmill-parser-wasm/tests/wasm.rs b/backend/parsers/windmill-parser-wasm/tests/wasm.rs index e8dc1e986b..35845e179a 100644 --- a/backend/parsers/windmill-parser-wasm/tests/wasm.rs +++ b/backend/parsers/windmill-parser-wasm/tests/wasm.rs @@ -339,7 +339,7 @@ fn test_parse_imports() -> anyhow::Result<()> { import { bar } from \"bar/foo/d\"; import { bar as baroof } from \"bar\"; "; - let mut l = parse_expr_for_imports(code)?; + let mut l = parse_expr_for_imports(code, false)?; l.sort(); assert_eq!( l, @@ -360,7 +360,7 @@ fn test_parse_imports_dts() -> anyhow::Result<()> { let code = " export type foo = number "; - let mut l = parse_expr_for_imports(code)?; + let mut l = parse_expr_for_imports(code, false)?; l.sort(); assert_eq!(l, vec![] as Vec); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index f3bcc40e2d..489ae6a954 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.501.2 + version: 1.501.3 title: Windmill API contact: diff --git a/backend/windmill-api/src/embeddings.rs b/backend/windmill-api/src/embeddings.rs index aba907ec16..9b3d21844a 100644 --- a/backend/windmill-api/src/embeddings.rs +++ b/backend/windmill-api/src/embeddings.rs @@ -24,7 +24,7 @@ use candle_nn::VarBuilder; #[cfg(feature = "embedding")] use candle_transformers::models::bert::{BertModel, Config, DTYPE}; #[cfg(feature = "embedding")] -use hf_hub::{api::sync::Api, Cache, Repo}; +use hf_hub::api::tokio::Api; #[cfg(feature = "embedding")] use serde::Deserialize; #[cfg(feature = "embedding")] @@ -158,63 +158,22 @@ pub struct ModelInstance { #[cfg(feature = "embedding")] impl ModelInstance { pub async fn load_model_files() -> Result<(PathBuf, PathBuf, PathBuf)> { - let repo = Repo::model("thenlper/gte-small".to_string()); - - let cache = Cache::default().repo(repo.clone()); - let api = Api::new()?; - let api = api.repo(repo); + let repo_api = api.model("thenlper/gte-small".to_string()); - let (config_filename, tokenizer_filename, weights_filename) = ( - cache - .get("config.json") - .or_else(|| { - api.get("config.json") - .or_else(|e| { - tracing::error!("Failed to get config.json from hugging face: {}", e); - return Err(e); - }) - .ok() - }) - .ok_or(Error::msg("could not get config.json"))?, - cache - .get("tokenizer.json") - .or_else(|| { - api.get("tokenizer.json") - .or_else(|e| { - tracing::error!( - "Failed to get tokenizer.json from hugging face: {}", - e - ); - return Err(e); - }) - .ok() - }) - .ok_or(Error::msg("could not get tokenizer.json"))?, - cache - .get("model.safetensors") - .and_then(|p| { - tracing::info!("Found embedding model in cache"); - Some(p) - }) - .or_else(|| { - tracing::info!("Downloading embedding model..."); - api.get("model.safetensors") - .or_else(|e| { - tracing::error!( - "Failed to get model.safetensors from hugging face: {}", - e - ); - return Err(e); - }) - .ok() - .and_then(|p| { - tracing::info!("Downloaded embedding model"); - Some(p) - }) - }) - .ok_or(Error::msg("could not get model.safetensors"))?, - ); + let (config_filename, tokenizer_filename, weights_filename) = + ( + repo_api + .get("config.json") + .await + .map_err(|e| anyhow!("Failed to get config.json from hugging face: {}", e))?, + repo_api.get("tokenizer.json").await.map_err(|e| { + anyhow!("Failed to get tokenizer.json from hugging face: {}", e) + })?, + repo_api.get("model.safetensors").await.map_err(|e| { + anyhow!("Failed to get model.safetensors from hugging face: {}", e) + })?, + ); Ok((config_filename, tokenizer_filename, weights_filename)) } diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 9d75d6e6e3..38bd67e675 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -36,8 +36,10 @@ use anyhow::Context; use argon2::Argon2; use axum::extract::DefaultBodyLimit; use axum::{middleware::from_extractor, routing::get, routing::post, Extension, Router}; +use axum::response::Response; +use axum::http::HeaderValue; +use axum::body::Body; use db::DB; -use http::HeaderValue; use reqwest::Client; #[cfg(feature = "oauth2")] use std::collections::HashMap; @@ -237,6 +239,7 @@ lazy_static::lazy_static! { } + // Compliance with cloud events spec. pub async fn add_webhook_allowed_origin( req: axum::extract::Request, @@ -259,6 +262,7 @@ pub async fn add_webhook_allowed_origin( next.run(req).await } + #[cfg(not(feature = "tantivy"))] type IndexReader = (); @@ -892,12 +896,18 @@ async fn ee_license() -> String { } } -async fn openapi() -> &'static str { - include_str!("../openapi-deref.yaml") +async fn openapi() -> Response { + Response::builder() + .header("content-type", "application/yaml") + .body(Body::from(include_str!("../openapi-deref.yaml"))) + .unwrap() } -async fn openapi_json() -> &'static str { - include_str!("../openapi-deref.json") +async fn openapi_json() -> Response { + Response::builder() + .header("content-type", "application/json") + .body(Body::from(include_str!("../openapi-deref.json"))) + .unwrap() } pub async fn migrate_db(db: &DB) -> anyhow::Result>> { diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index d5656fb6c1..48bd94081c 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -10,6 +10,8 @@ use axum::{body::Body, extract::OriginalUri, http::Response, response::IntoRespo #[cfg(feature = "static_frontend")] use axum::http::header; +#[cfg(feature = "static_frontend")] +use http::HeaderValue; use hyper::Uri; #[cfg(feature = "static_frontend")] @@ -17,6 +19,12 @@ use mime_guess::mime; #[cfg(feature = "static_frontend")] use rust_embed::RustEmbed; +// Content Security Policy configuration +#[cfg(feature = "static_frontend")] +lazy_static::lazy_static! { + static ref CSP_POLICY: String = std::env::var("CSP_POLICY").unwrap_or_default(); +} + // static_handler is a handler that serves static files from the pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFile { StaticFile(original_uri) @@ -51,6 +59,13 @@ fn serve_path(path: &str) -> Response { let mut res = Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + + // Add Content-Security-Policy header for static assets when policy is set + if !CSP_POLICY.is_empty() { + if let Ok(header_value) = HeaderValue::try_from(CSP_POLICY.as_str()) { + res = res.header("Content-Security-Policy", header_value); + } + } if mime.as_ref() == mime::APPLICATION_JAVASCRIPT || mime.as_ref() == mime::TEXT_JAVASCRIPT || path.ends_with(".wasm") diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 90be4e9cfc..ad46bfd3cd 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -66,10 +66,10 @@ lazy_static::lazy_static! { lazy_static::lazy_static! { static ref PIPTAR_UPLOAD_CHANNEL: tokio::sync::mpsc::UnboundedSender = { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - + // Spawn background task to handle uploads sequentially tokio::spawn(handle_piptar_uploads(rx)); - + tx }; } @@ -85,7 +85,7 @@ struct PiptarUploadTask { async fn handle_piptar_uploads(mut rx: tokio::sync::mpsc::UnboundedReceiver) { use crate::global_cache::build_tar_and_push; use windmill_common::s3_helpers::get_object_store; - + while let Some(task) = rx.recv().await { if let Some(os) = get_object_store().await { match build_tar_and_push(os, task.venv_path.clone(), task.cache_dir, None, false).await { @@ -324,6 +324,7 @@ pub async fn uv_pip_compile( child_cmd .env("SystemRoot", SYSTEM_ROOT.as_str()) .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env("HOME", crate::USERPROFILE_ENV.as_str()) .env( "LOCALAPPDATA", std::env::var("LOCALAPPDATA") @@ -332,6 +333,29 @@ pub async fn uv_pip_compile( .env( "TMP", std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ) + .env( + "APPDATA", + std::env::var("APPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Roaming", crate::USERPROFILE_ENV.as_str())), + ) + .env( + "ComSpec", + std::env::var("ComSpec").unwrap_or_else(|_| String::from("C:\\Windows\\System32\\cmd.exe")), + ) + .env( + "PATHEXT", + std::env::var("PATHEXT").unwrap_or_else(|_| + String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL") + ), + ) + .env( + "ProgramData", + std::env::var("ProgramData").unwrap_or_else(|_| String::from("C:\\ProgramData")), + ) + .env( + "ProgramFiles", + std::env::var("ProgramFiles").unwrap_or_else(|_| String::from("C:\\Program Files")), ); } @@ -602,7 +626,7 @@ pub async fn handle_python_job( if v == '': del pre_args[k] kwargs = inner_script.preprocessor(**pre_args) - kwrags_json = res_to_json(kwargs) + kwrags_json = res_to_json(kwargs) with open("args.json", 'w') as f: f.write(kwrags_json)"# ) @@ -678,7 +702,7 @@ except BaseException as e: tb = traceback.format_tb(exc_traceback) with open(result_json, 'w') as f: err = {{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }} - extra = e.__dict__ + extra = e.__dict__ if extra and len(extra) > 0: err['extra'] = extra flow_node_id = os.environ.get('WM_FLOW_STEP_ID') @@ -1417,6 +1441,7 @@ async fn spawn_uv_install( .envs(PROXY_ENVS.clone()) .env("SystemRoot", SYSTEM_ROOT.as_str()) .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env("HOME", HOME_ENV.as_str()) .env( "TMP", std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), @@ -1426,6 +1451,29 @@ async fn spawn_uv_install( std::env::var("LOCALAPPDATA") .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), ) + .env( + "APPDATA", + std::env::var("APPDATA") + .unwrap_or_else(|_| format!("{}\\AppData\\Roaming", crate::USERPROFILE_ENV.as_str())), + ) + .env( + "ComSpec", + std::env::var("ComSpec").unwrap_or_else(|_| String::from("C:\\Windows\\System32\\cmd.exe")), + ) + .env( + "PATHEXT", + std::env::var("PATHEXT").unwrap_or_else(|_| + String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL") + ), + ) + .env( + "ProgramData", + std::env::var("ProgramData").unwrap_or_else(|_| String::from("C:\\ProgramData")), + ) + .env( + "ProgramFiles", + std::env::var("ProgramFiles").unwrap_or_else(|_| String::from("C:\\Program Files")), + ) .args(&command_args[1..]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -1810,7 +1858,7 @@ pub async fn handle_python_reqs( // Create a file to indicate that installation was successfull let valid_path = venv_p.clone() + "/.valid.windmill"; - // This is atomic operation, meaning, that it either completes and wheel is valid, + // This is atomic operation, meaning, that it either completes and wheel is valid, // or it does not and wheel is invalid and will be reinstalled next run if let Err(e) = File::create(&valid_path).await{ tracing::error!( @@ -1942,7 +1990,7 @@ pub async fn handle_python_reqs( venv_path: venv_p.clone(), cache_dir: py_version.to_cache_dir_top_level(false), }; - + if let Err(e) = PIPTAR_UPLOAD_CHANNEL.send(upload_task) { tracing::warn!("Failed to queue piptar upload for {venv_p}: {e}"); } else { @@ -1961,7 +2009,7 @@ pub async fn handle_python_reqs( pids.lock().await.get_mut(i).and_then(|e| e.take()); // Create a file to indicate that installation was successfull let valid_path = venv_p.clone() + "/.valid.windmill"; - // This is atomic operation, meaning, that it either completes and wheel is valid, + // This is atomic operation, meaning, that it either completes and wheel is valid, // or it does not and wheel is invalid and will be reinstalled next run if let Err(e) = File::create(&valid_path).await{ tracing::error!( diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 47bb07c438..7fee5932e4 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -188,7 +188,7 @@ fn try_normalize(path: &Path) -> Option { fn parse_ts_relative_imports(raw_code: &str, script_path: &str) -> error::Result> { let mut relative_imports = vec![]; - let r = parse_expr_for_imports(raw_code)?; + let r = parse_expr_for_imports(raw_code, true)?; for import in r { let import = import.trim_end_matches(".ts"); if import.starts_with("/") { diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 5d581078e5..820516e7cc 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.501.2"; +export const VERSION = "v1.501.3"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/main.ts b/cli/main.ts index 78120b1874..598eb8ab3e 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -63,7 +63,7 @@ export { // } // }); -export const VERSION = "1.501.2"; +export const VERSION = "1.501.3"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b18530b2cb..1cc16f94e1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.501.2", + "version": "1.501.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.501.2", + "version": "1.501.3", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 742399f0b7..2aea83328b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.501.2", + "version": "1.501.3", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/ArrayTypeNarrowing.svelte b/frontend/src/lib/components/ArrayTypeNarrowing.svelte index 865aadc8ca..86b4fb6c10 100644 --- a/frontend/src/lib/components/ArrayTypeNarrowing.svelte +++ b/frontend/src/lib/components/ArrayTypeNarrowing.svelte @@ -8,6 +8,8 @@ import Alert from './common/alert/Alert.svelte' import EditableSchemaDrawer from './schema/EditableSchemaDrawer.svelte' import type { SchemaProperty } from '$lib/common' + import Toggle from './Toggle.svelte' + import { tick } from 'svelte' interface Props { canEditResourceType?: boolean @@ -48,20 +50,6 @@ ? 'bytes' : 'string' ) - - let schema = $state({ - properties: itemsType?.properties || {}, - order: Object.keys(itemsType?.properties || {}), - required: Object.values(itemsType?.properties || {}).map((p) => p.required) - }) - - function updateItemsType() { - itemsType = { - ...itemsType, - properties: schema.properties, - type: 'object' - } - } {#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'} @@ -180,10 +168,40 @@ {/if} {#if selected === 'object'} - { - updateItemsType() - }} + { + return itemsType?.properties != undefined + }, + async (v) => { + await tick() + if (v) { + itemsType = { type: 'object', properties: {} } + } else { + itemsType = { type: 'object', properties: undefined } + } + } + } + options={{ left: 'JSON', right: 'Custom Object' }} /> + {#if itemsType?.properties != undefined} + { + return { + properties: itemsType?.properties || {}, + order: Object.keys(itemsType?.properties || {}), + required: Object.values(itemsType?.properties || {}).map((p) => p.required) + } + }, + (schema) => { + itemsType = { + ...itemsType, + properties: schema.properties, + type: 'object' + } + } + } + /> + {/if} {/if} diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 8bad2713fd..dbeebfa6f7 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -75,6 +75,11 @@ import { Triggers } from './triggers/triggers.svelte' import { TestSteps } from './flows/testSteps.svelte' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' + import { + setStepHistoryLoaderContext, + StepHistoryLoader, + type stepState + } from './stepHistoryLoader.svelte' interface Props { initialPath?: string @@ -96,6 +101,11 @@ draftTriggersFromUrl?: Trigger[] | undefined selectedTriggerIndexFromUrl?: number | undefined children?: import('svelte').Snippet + loadedFromHistoryFromUrl?: { + flowJobInitial: boolean | undefined + stepsState: Record + } + noInitial?: boolean } let { @@ -117,7 +127,9 @@ setSavedraftCb = undefined, draftTriggersFromUrl = undefined, selectedTriggerIndexFromUrl = undefined, - children + children, + loadedFromHistoryFromUrl, + noInitial = false }: Props = $props() let initialPathStore = writable(initialPath) @@ -533,7 +545,11 @@ path: $pathStore, selectedId: $selectedIdStore, draft_triggers: triggersState.getDraftTriggersSnapshot(), - selected_trigger: triggersState.getSelectedTriggerSnapshot() + selected_trigger: triggersState.getSelectedTriggerSnapshot(), + loadedFromHistory: { + flowJobInitial: stepHistoryLoader.flowJobInitial, + stepsState: stepHistoryLoader.stepStates + } }) ) } catch (err) { @@ -833,6 +849,40 @@ const hasAiDiff = aiChatManager.flowAiChatHelpers?.hasDiff() ?? false customUi && untrack(() => onCustomUiChange(customUi, hasAiDiff)) }) + + export async function loadFlowState() { + await stepHistoryLoader.loadIndividualStepsStates( + flowStore.val as Flow, + flowStateStore, + $workspaceStore!, + $initialPathStore, + $pathStore + ) + } + + let stepHistoryLoader = new StepHistoryLoader( + loadedFromHistoryFromUrl?.stepsState ?? {}, + loadedFromHistoryFromUrl?.flowJobInitial, + saveSessionDraft, + noInitial + ) + setStepHistoryLoaderContext(stepHistoryLoader) + + export function setLoadedFromHistory( + loadedFromHistoryUrl: + | { + flowJobInitial: boolean | undefined + stepsState: Record + } + | undefined + ) { + if (!loadedFromHistoryUrl) { + return + } + + stepHistoryLoader.setFlowJobInitial(loadedFromHistoryUrl.flowJobInitial) + stepHistoryLoader.stepStates = loadedFromHistoryUrl.stepsState + } @@ -1093,6 +1143,9 @@ }} {forceTestTab} {highlightArg} + onRunPreview={() => { + flowPreviewButtons?.openPreview(true) + }} /> {:else} Loading... diff --git a/frontend/src/lib/components/FlowPreviewContent.svelte b/frontend/src/lib/components/FlowPreviewContent.svelte index 9e641a3a6c..222173e45d 100644 --- a/frontend/src/lib/components/FlowPreviewContent.svelte +++ b/frontend/src/lib/components/FlowPreviewContent.svelte @@ -18,9 +18,9 @@ import Toggle from './Toggle.svelte' import JsonInputs from './JsonInputs.svelte' import FlowHistoryJobPicker from './FlowHistoryJobPicker.svelte' - import { NEVER_TESTED_THIS_FAR } from './flows/models' import { writable, type Writable } from 'svelte/store' import type { DurationStatus, GraphModuleState } from './graph' + import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' import { stateSnapshot } from '$lib/svelte5Utils.svelte' @@ -30,7 +30,6 @@ export let jobId: string | undefined = undefined export let job: Job | undefined = undefined - export let initial: boolean = false export let selectedJobStep: string | undefined = undefined export let selectedJobStepIsTopLevel: boolean | undefined = undefined @@ -71,6 +70,8 @@ } = getContext('FlowEditorContext') const dispatch = createEventDispatcher() + let stepHistoryLoader = getStepHistoryLoaderContext() + let renderCount: number = 0 let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined let currentJobId: string | undefined = undefined @@ -96,8 +97,8 @@ args: Record, restartedFrom: RestartedFrom | undefined ) { - if (initial) { - initial = false + if (stepHistoryLoader?.flowJobInitial) { + stepHistoryLoader?.setFlowJobInitial(false) } try { lastPreviewFlow = JSON.stringify(flowStore.val) @@ -186,44 +187,6 @@ $: selectedJobStep !== undefined && onSelectedJobStepChange() - async function loadIndividualStepsStates() { - // console.log('loadIndividualStepsStates') - dfs(flowStore.val.value.modules, async (module) => { - // console.log('module', $flowStateStore[module.id], module.id) - const prev = $flowStateStore[module.id]?.previewResult - if (prev && prev != NEVER_TESTED_THIS_FAR) { - return - } - const previousJobId = await JobService.listJobs({ - workspace: $workspaceStore!, - scriptPathExact: - `path` in module.value - ? module.value.path - : ($initialPathStore == '' ? $pathStore : $initialPathStore) + '/' + module.id, - jobKinds: ['preview', 'script', 'flowpreview', 'flow', 'flowscript'].join(','), - page: 1, - perPage: 1 - }) - // console.log('previousJobId', previousJobId, module.id) - - if (previousJobId.length > 0) { - const getJobResult = await JobService.getCompletedJobResultMaybe({ - workspace: $workspaceStore!, - id: previousJobId[0].id - }) - if ('result' in getJobResult) { - $flowStateStore[module.id] = { - ...($flowStateStore[module.id] ?? {}), - previewResult: getJobResult.result, - previewJobId: previousJobId[0].id, - previewWorkspaceId: previousJobId[0].workspace_id, - previewSuccess: getJobResult.success - } - } - } - }) - } - let scrollableDiv: HTMLDivElement | undefined = undefined function handleScroll() { scrollTop = scrollableDiv?.scrollTop ?? 0 @@ -486,16 +449,15 @@ > { - loadIndividualStepsStates() - }} on:select={(e) => { if (!currentJobId) { currentJobId = jobId } const detail = e.detail - initial = detail.initial jobId = detail.jobId + if (detail.initial && stepHistoryLoader?.flowJobInitial === undefined) { + stepHistoryLoader?.setFlowJobInitial(detail.initial) + } }} on:unselect={() => { jobId = currentJobId @@ -505,12 +467,12 @@ /> {#if jobId} - {#if initial} + {#if stepHistoryLoader?.flowJobInitial}
{ - initial = false + stepHistoryLoader?.setFlowJobInitial(false) }} class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 rounded bg-gray-500/40 absolute top-0 left-0 w-full z-50" > @@ -532,12 +494,6 @@ on:done={() => { $executionCount = $executionCount + 1 }} - on:jobsLoaded={() => { - if (initial) { - console.log('loading initial steps after initial job loaded') - loadIndividualStepsStates() - } - }} bind:selectedJobStep bind:rightColumnSelect /> diff --git a/frontend/src/lib/components/ModulePreviewResultViewer.svelte b/frontend/src/lib/components/ModulePreviewResultViewer.svelte index ec203979c9..35b4df1ebb 100644 --- a/frontend/src/lib/components/ModulePreviewResultViewer.svelte +++ b/frontend/src/lib/components/ModulePreviewResultViewer.svelte @@ -24,6 +24,7 @@ disableMock?: boolean disableHistory?: boolean onUpdateMock?: (mock: { enabled: boolean; return_value?: unknown }) => void + loadingHistory?: boolean } let { @@ -38,7 +39,8 @@ testIsLoading = false, disableMock = false, disableHistory = false, - onUpdateMock + onUpdateMock, + loadingHistory = false }: Props = $props() const { testSteps } = getContext('FlowEditorContext') @@ -82,7 +84,7 @@ mock={mod.mock} bind:forceJson bind:selectedJob - isLoading={(testIsLoading && !scriptProgress) || fetchingLastJob} + isLoading={(testIsLoading && !scriptProgress) || fetchingLastJob || loadingHistory} bind:preview path={`path` in mod.value ? mod.value.path : ''} {loopStatus} @@ -112,7 +114,9 @@ duration={selectedJob?.['duration_ms']} mem={selectedJob?.['mem_peak']} content={selectedJob?.logs} - isLoading={(testIsLoading && selectedJob?.['running'] == false) || fetchingLastJob} + isLoading={(testIsLoading && selectedJob?.['running'] == false) || + fetchingLastJob || + loadingHistory} tag={selectedJob?.tag} /> {/if} diff --git a/frontend/src/lib/components/ModuleTest.svelte b/frontend/src/lib/components/ModuleTest.svelte index 5ade2164c8..63cd0b58ea 100644 --- a/frontend/src/lib/components/ModuleTest.svelte +++ b/frontend/src/lib/components/ModuleTest.svelte @@ -15,6 +15,7 @@ import { getContext, onMount } from 'svelte' import type { FlowEditorContext } from './flows/types' import TestJobLoader from './TestJobLoader.svelte' + import { getStepHistoryLoaderContext } from './stepHistoryLoader.svelte' interface Props { mod: FlowModule @@ -37,6 +38,7 @@ let testJobLoader: TestJobLoader | undefined = $state(undefined) let jobProgressReset: () => void = () => {} + let stepHistoryLoader = getStepHistoryLoaderContext() export function runTestWithStepArgs() { runTest(testSteps.getStepArgs(mod.id)?.value) @@ -92,6 +94,7 @@ $flowStateStore[mod.id].previewWorkspaceId = testJob.workspace_id $flowStateStore = $flowStateStore } + stepHistoryLoader?.resetInitial(mod.id) } testJob = undefined } diff --git a/frontend/src/lib/components/SchemaForm.svelte b/frontend/src/lib/components/SchemaForm.svelte index 4168229f4d..c3dbbf347b 100644 --- a/frontend/src/lib/components/SchemaForm.svelte +++ b/frontend/src/lib/components/SchemaForm.svelte @@ -163,23 +163,21 @@ if (!deepEqual(schema?.order, lkeys) || !deepEqual(keys, lkeys)) { if (schema?.order && Array.isArray(schema.order)) { const n = {} - ;(schema.order as string[]).forEach((x) => { if (schema.properties && schema.properties[x] != undefined) { n[x] = schema.properties[x] } }) - Object.keys(schema.properties ?? {}) .filter((x) => !schema.order?.includes(x)) .forEach((x) => { n[x] = schema.properties[x] }) - if (!deepEqual(schema.properties, n)) { + if (!deepEqual(schema.properties, n) || !deepEqual(schema.properties, Object.keys(n))) { schema.properties = n } } - let nkeys = [...new Set(Object.keys(schema.properties ?? {}))] + let nkeys = Object.keys(schema.properties ?? {}) if (!deepEqual(keys, nkeys)) { keys = nkeys @@ -261,7 +259,8 @@
{/if} - + +
{label} - {#if lang === 'ansible' || lang === 'nu'} + {#if lang === 'nu'} BETA {/if} diff --git a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte index 3cec5c1868..8ec9475f30 100644 --- a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte +++ b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte @@ -1,6 +1,6 @@ {#each Object.entries(components['buttoncomponent'].initialData.configuration) as [key, initialConfig] (key)} diff --git a/frontend/src/lib/components/flows/FlowEditor.svelte b/frontend/src/lib/components/flows/FlowEditor.svelte index 10bac05c3b..8d9b0af8cb 100644 --- a/frontend/src/lib/components/flows/FlowEditor.svelte +++ b/frontend/src/lib/components/flows/FlowEditor.svelte @@ -36,6 +36,7 @@ onEditInput?: ((moduleId: string, key: string) => void) | undefined forceTestTab?: Record highlightArg?: Record + onRunPreview?: () => void } let { @@ -52,7 +53,8 @@ onTestUpTo = undefined, onEditInput = undefined, forceTestTab, - highlightArg + highlightArg, + onRunPreview = () => {} }: Props = $props() let flowModuleSchemaMap: FlowModuleSchemaMap | undefined = $state() @@ -128,6 +130,7 @@ {onDeployTrigger} {forceTestTab} {highlightArg} + {onRunPreview} /> {/if} diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 41b3d15827..566b2cddbf 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -27,6 +27,7 @@ onDeployTrigger?: (trigger: Trigger) => void forceTestTab?: Record highlightArg?: Record + onRunPreview?: () => void } let { @@ -37,7 +38,8 @@ savedFlow = undefined, onDeployTrigger = () => {}, forceTestTab, - highlightArg + highlightArg, + onRunPreview }: Props = $props() const { @@ -82,6 +84,7 @@ showCaptureHint.set(true) }} on:applyArgs + {onRunPreview} /> {:else if $selectedId === 'Result'}

The result of the flow will be the result of the last node.

diff --git a/frontend/src/lib/components/flows/content/FlowInput.svelte b/frontend/src/lib/components/flows/content/FlowInput.svelte index 1c3ce16070..421d2e050b 100644 --- a/frontend/src/lib/components/flows/content/FlowInput.svelte +++ b/frontend/src/lib/components/flows/content/FlowInput.svelte @@ -4,7 +4,6 @@ import { getContext, tick, untrack } from 'svelte' import FlowCard from '../common/FlowCard.svelte' import type { FlowEditorContext } from '../types' - import Drawer from '$lib/components/common/drawer/Drawer.svelte' import JsonInputs from '$lib/components/JsonInputs.svelte' import { convert } from '@redocly/json-to-json-schema' import { sendUserToast } from '$lib/toast' @@ -27,7 +26,6 @@ Check } from 'lucide-svelte' import CaptureIcon from '$lib/components/triggers/CaptureIcon.svelte' - import FlowPreviewContent from '$lib/components/FlowPreviewContent.svelte' import FlowInputEditor from './FlowInputEditor.svelte' import { twMerge } from 'tailwind-merge' import CaptureButton from '$lib/components/triggers/CaptureButton.svelte' @@ -48,9 +46,10 @@ interface Props { noEditor: boolean disabled: boolean + onRunPreview?: () => void } - let { noEditor, disabled }: Props = $props() + let { noEditor, disabled, onRunPreview }: Props = $props() const { flowStore, @@ -76,7 +75,6 @@ let runDisabled: boolean = $state(false) let editableSchemaForm: EditableSchemaForm | undefined = $state(undefined) let savedPreviewArgs: Record | undefined = $state(undefined) - let preventEscape = $state(false) let isValid = $state(true) function updateEditPanelSize(size: number | undefined) { @@ -193,9 +191,6 @@ return { required: [], properties: {}, ...convert(parsed) } } - let flowPreviewContent: FlowPreviewContent | undefined = $state() - let previewOpen = $state(false) - function handleKeydown(event: KeyboardEvent) { if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { runPreview() @@ -211,8 +206,7 @@ if (previewArguments) { previewArgs.val = structuredClone($state.snapshot(previewArguments)) } - previewOpen = true - flowPreviewContent?.test() + onRunPreview?.() } function updatePreviewSchemaAndArgs(payload: any) { @@ -359,18 +353,6 @@ - - { - previewOpen = false - }} - bind:preventEscape - /> - - {#if !disabled}
diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 2388679010..9d5dad9154 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -53,6 +53,7 @@ import ModulePreviewResultViewer from '$lib/components/ModulePreviewResultViewer.svelte' import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte' import { refreshStateStore } from '$lib/svelte5Utils.svelte' + import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte' const { selectedId, @@ -186,6 +187,7 @@ let forceReload = $state(0) let editorPanelSize = $state(noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50) let editorSettingsPanelSize = $state(100 - untrack(() => editorPanelSize)) + let stepHistoryLoader = getStepHistoryLoaderContext() function onSelectedIdChange() { if (!$flowStateStore?.[$selectedId]?.schema && flowModule) { @@ -820,7 +822,23 @@
{#if selected === 'test'} - + + {#if stepHistoryLoader?.stepStates[flowModule.id]?.initial && !flowModule.mock?.enabled} + + +
{ + stepHistoryLoader?.resetInitial(flowModule.id) + }} + class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 bg-gray-500/40 absolute top-0 left-0 w-full z-50" + > +
Run loaded from history
+
+ {/if}
{/if} diff --git a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte index 254df4ba6c..80708fea50 100644 --- a/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte +++ b/frontend/src/lib/components/flows/header/FlowPreviewButtons.svelte @@ -4,7 +4,7 @@ import Drawer from '$lib/components/common/drawer/Drawer.svelte' import FlowPreviewContent from '$lib/components/FlowPreviewContent.svelte' import type { Job } from '$lib/gen' - import { createEventDispatcher } from 'svelte' + import { createEventDispatcher, tick } from 'svelte' import { getContext } from 'svelte' import type { FlowEditorContext } from '../types' @@ -26,6 +26,7 @@ export async function openPreview(test: boolean = false) { if (!previewOpen) { previewOpen = true + await tick() flowPreviewContent?.refresh() if (!test) return } @@ -38,7 +39,6 @@ let jobId: string | undefined = $state(undefined) let job: Job | undefined = $state(undefined) let preventEscape = $state(false) - let initial = $state(false) let selectedJobStep: string | undefined = $state(undefined) let selectedJobStepIsTopLevel: boolean | undefined = $state(undefined) let selectedJobStepType: 'single' | 'forloop' | 'branchall' = $state('single') @@ -120,7 +120,6 @@ bind:previewMode bind:job bind:jobId - bind:initial bind:selectedJobStep bind:selectedJobStepIsTopLevel bind:selectedJobStepType diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte index 4d17fb4f2a..4628002112 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte @@ -40,6 +40,7 @@ } from '$lib/components/copilot/chat/flow/ModuleAcceptReject.svelte' import { Button } from '$lib/components/common' import ModuleTest from '$lib/components/ModuleTest.svelte' + import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte' interface Props { selected?: boolean @@ -139,6 +140,8 @@ let flowStateStore = $derived(flowEditorContext?.flowStateStore) + let stepHistoryLoader = getStepHistoryLoaderContext() + function updateConnectingData( id: string | undefined, pickableIds: Record | undefined, @@ -449,7 +452,9 @@ bind:derivedHistoryOpen={historyOpen} historyOffset={{ mainAxis: 12, crossAxis: -9 }} clazz="p-1" - isLoading={testIsLoading} + isLoading={testIsLoading || + (id ? stepHistoryLoader?.stepStates[id]?.loadingJobs : false)} + initial={id ? stepHistoryLoader?.stepStates[id]?.initial : undefined} /> {/snippet} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index dd58edb8c7..b52fba3bee 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -38,6 +38,7 @@ import { dfsByModule } from '../previousResults' import type { InlineScript, InsertKind } from '$lib/components/graph/graphBuilder.svelte' import { refreshStateStore } from '$lib/svelte5Utils.svelte' + import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte' interface Props { sidebarSize?: number | undefined @@ -239,10 +240,18 @@ } } + let stepHistoryLoader = getStepHistoryLoaderContext() + async function loadLastJob(path: string, moduleId: string) { if (!path) { return } + if (stepHistoryLoader) { + stepHistoryLoader.stepStates[moduleId] = { + initial: true, + loadingJobs: true + } + } const previousJobId = await JobService.listJobs({ workspace: $workspaceStore!, scriptPathExact: path, @@ -263,6 +272,9 @@ previewWorkspaceId: previousJobId[0].workspace_id, previewSuccess: getJobResult.success } + if (stepHistoryLoader) { + stepHistoryLoader.stepStates[moduleId].loadingJobs = false + } } $flowStateStore = $flowStateStore } diff --git a/frontend/src/lib/components/flows/propPicker/OutputPicker.svelte b/frontend/src/lib/components/flows/propPicker/OutputPicker.svelte index b62326352f..b9f3de6798 100644 --- a/frontend/src/lib/components/flows/propPicker/OutputPicker.svelte +++ b/frontend/src/lib/components/flows/propPicker/OutputPicker.svelte @@ -20,6 +20,8 @@ bottomBarOpen?: boolean loopStatus?: { type: 'inside' | 'self'; flow: 'forloopflow' | 'whileloopflow' } | undefined onEditInput?: (moduleId: string, key: string) => void + initial?: boolean + onResetInitial?: () => void } let { @@ -184,7 +186,7 @@ }} bind:this={popover} allowFullScreen - contentClasses="overflow-hidden resize" + contentClasses="overflow-hidden resize relative" contentStyle={`width: calc(${MIN_WIDTH}px); min-width: calc(${MIN_WIDTH}px); height: calc(${MIN_HEIGHT}px); min-height: calc(${MIN_HEIGHT}px); `} extraProps={{ 'data-prop-picker': true }} closeOnOtherPopoverOpen diff --git a/frontend/src/lib/components/flows/propPicker/OutputPickerInner.svelte b/frontend/src/lib/components/flows/propPicker/OutputPickerInner.svelte index 37700271cd..c8534d0a00 100644 --- a/frontend/src/lib/components/flows/propPicker/OutputPickerInner.svelte +++ b/frontend/src/lib/components/flows/propPicker/OutputPickerInner.svelte @@ -28,6 +28,7 @@ import { twMerge } from 'tailwind-merge' import DisplayResultControlBar from '$lib/components/DisplayResultControlBar.svelte' import { base } from '$lib/base' + import { fade } from 'svelte/transition' interface Props { prefix?: string @@ -64,6 +65,8 @@ onUpdateMock?: (mock: { enabled: boolean; return_value?: unknown }) => void onEditInput?: (moduleId: string, key: string) => void selectionId?: string + initial?: boolean + onResetInitial?: () => void } let { @@ -95,7 +98,8 @@ onSelect, onUpdateMock, onEditInput, - selectionId + selectionId, + initial }: Props = $props() type SelectedJob = @@ -252,6 +256,8 @@ let popoverHeight = $derived(customHeight ?? (clientHeight > 0 ? clientHeight : 0)) + const isLoadingAndNotMock = $derived(isLoading && !mock?.enabled) + const copilot_fix_render = $derived(copilot_fix) @@ -353,7 +359,7 @@ {/snippet} {/if} - {#if !isLoading} + {#if !isLoadingAndNotMock || mock?.enabled}
{/if} - {#if !disableMock && !isLoading} + {#if !disableMock && !isLoadingAndNotMock}
+ {#if initial && !mock?.enabled} + Run loaded from history + {/if} {#snippet editKey(key: string)} diff --git a/frontend/src/lib/components/schema/PropertyEditor.svelte b/frontend/src/lib/components/schema/PropertyEditor.svelte index cf02d80fb0..5f7e2d8b23 100644 --- a/frontend/src/lib/components/schema/PropertyEditor.svelte +++ b/frontend/src/lib/components/schema/PropertyEditor.svelte @@ -1,6 +1,4 @@ diff --git a/frontend/src/lib/components/select/Select.svelte b/frontend/src/lib/components/select/Select.svelte index 69fb7bf16c..35a4a90455 100644 --- a/frontend/src/lib/components/select/Select.svelte +++ b/frontend/src/lib/components/select/Select.svelte @@ -1,4 +1,4 @@ - diff --git a/lsp/Pipfile b/lsp/Pipfile index 3b4472d637..4be1f82a96 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.501.2" -wmill_pg = ">=1.501.2" +wmill = ">=1.501.3" +wmill_pg = ">=1.501.3" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 2d8e5227ea..1849aba49f 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.501.2 + version: 1.501.3 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index f228f701c9..2e077d3b45 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.501.2' + ModuleVersion = '1.501.3' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 3ba482a488..a658e5b29f 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.501.2" +version = "1.501.3" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index d883c89f27..14e7135428 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.501.2" +version = "1.501.3" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 579db6fc5c..28a6233958 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.501.2", + "version": "1.501.3", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 3ff27445aa..d39e265a09 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.501.2", + "version": "1.501.3", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index d63734cecb..1319376aa7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.501.2 +1.501.3