merge: sync with origin/main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## [1.649.0](https://github.com/windmill-labs/windmill/compare/v1.648.0...v1.649.0) (2026-03-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** add script recorder for offline replay ([#8200](https://github.com/windmill-labs/windmill/issues/8200)) ([c97d8b4](https://github.com/windmill-labs/windmill/commit/c97d8b4715f86ea83ab2c0223ba859ced690829a))
|
||||
* move index management out of /srch/, add storage size reporting ([#8169](https://github.com/windmill-labs/windmill/issues/8169)) ([ee01acd](https://github.com/windmill-labs/windmill/commit/ee01acd9a6a2cd68a3f226988bfb46f6a6e64c08))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean up slow-load toast interval on component destroy ([#8207](https://github.com/windmill-labs/windmill/issues/8207)) ([26f4f2b](https://github.com/windmill-labs/windmill/commit/26f4f2b399b828185b553289d6560e12261030a3))
|
||||
* **frontend:** prevent subflow expansion from hiding all insertion points ([#8203](https://github.com/windmill-labs/windmill/issues/8203)) ([e97da86](https://github.com/windmill-labs/windmill/commit/e97da860672171e33054a77d71f4824bb09e540d))
|
||||
* gracefully handle malformed OAuth entries in instance config ([#8205](https://github.com/windmill-labs/windmill/issues/8205)) ([cac4bdd](https://github.com/windmill-labs/windmill/commit/cac4bdd54f0c3ea80844ac31f7597f418ff7d8ae))
|
||||
* skip stop_after_if evaluation for skipped (identity) flow steps ([#8201](https://github.com/windmill-labs/windmill/issues/8201)) ([e6f7775](https://github.com/windmill-labs/windmill/commit/e6f7775d4d9a052aefc37260c6ed161146841cd7))
|
||||
* use exact matching for python requirements directive parsing ([#8199](https://github.com/windmill-labs/windmill/issues/8199)) ([2b2be38](https://github.com/windmill-labs/windmill/commit/2b2be38f129bbe58b6bb3815c4bd94aa03a3da90))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* use two-step query in input history to leverage v2_job index ([#8197](https://github.com/windmill-labs/windmill/issues/8197)) ([50defdd](https://github.com/windmill-labs/windmill/commit/50defdded113b4d2cf0991b3fb642d1cd9a462b7))
|
||||
|
||||
## [1.648.0](https://github.com/windmill-labs/windmill/compare/v1.647.2...v1.648.0) (2026-03-02)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email FROM token WHERE token = $1 AND (expiration > NOW() OR expiration IS NULL)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "19a7ebb2e7e8e57b6e7c974da8eb7c6841a5c4ff12ba7c12c73d691c49dd99ed"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
@@ -860,9 +860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.16.0"
|
||||
version = "1.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9"
|
||||
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
@@ -870,9 +870,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.37.1"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549"
|
||||
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -1334,9 +1334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-xml"
|
||||
version = "0.60.14"
|
||||
version = "0.60.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa"
|
||||
checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
@@ -6173,20 +6173,20 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 6.0.0",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
@@ -7421,9 +7421,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
@@ -10723,6 +10723,12 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
@@ -13850,7 +13856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.1",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -15732,7 +15738,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15796,7 +15802,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15809,7 +15815,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15947,7 +15953,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15970,7 +15976,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15983,7 +15989,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16009,7 +16015,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16019,7 +16025,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16036,7 +16042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
@@ -16059,7 +16065,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16082,7 +16088,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16098,7 +16104,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16118,7 +16124,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16138,7 +16144,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16152,7 +16158,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16179,7 +16185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16204,7 +16210,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"flate2",
|
||||
@@ -16222,7 +16228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16243,7 +16249,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16263,7 +16269,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16293,7 +16299,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16320,7 +16326,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16332,7 +16338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.7.9",
|
||||
@@ -16355,7 +16361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16369,7 +16375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16399,7 +16405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16413,7 +16419,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16432,7 +16438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -16531,7 +16537,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16550,7 +16556,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16565,7 +16571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16589,7 +16595,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16606,7 +16612,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16622,7 +16628,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16643,7 +16649,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16674,7 +16680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-oauth2",
|
||||
@@ -16698,7 +16704,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -16732,7 +16738,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16750,7 +16756,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -16759,7 +16765,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16771,7 +16777,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16783,7 +16789,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -16795,7 +16801,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16807,7 +16813,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16819,7 +16825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -16830,7 +16836,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16841,7 +16847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16854,7 +16860,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16878,7 +16884,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16892,7 +16898,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -16909,7 +16915,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16924,7 +16930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16943,7 +16949,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -16954,7 +16960,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16991,7 +16997,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17029,7 +17035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -17040,7 +17046,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17069,7 +17075,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -17092,7 +17098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17125,7 +17131,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17145,7 +17151,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17179,7 +17185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17214,7 +17220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17237,7 +17243,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17261,7 +17267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17285,7 +17291,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17320,7 +17326,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17348,7 +17354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17371,7 +17377,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17389,7 +17395,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -76,7 +76,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.648.0"
|
||||
version = "1.649.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
9b3339730eb4bb0b564c7c56ac546f33fb3d8905
|
||||
6fd5a2ce908235a17975ad4dbdf0051cd89334f3
|
||||
|
||||
@@ -62,7 +62,7 @@ use windmill_common::{
|
||||
},
|
||||
worker::{
|
||||
is_native_mode_from_env, reload_custom_tags_setting, Connection, HUB_CACHE_DIR,
|
||||
HUB_RT_CACHE_DIR, NATIVE_MODE_RESOLVED, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP,
|
||||
HUB_RT_CACHE_DIR, NATIVE_MODE_RESOLVED, TMP_LOGS_DIR, WINDMILL_DIR, WORKER_GROUP,
|
||||
},
|
||||
KillpillSender, DEFAULT_HUB_BASE_URL, METRICS_ENABLED,
|
||||
};
|
||||
@@ -238,8 +238,8 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
)
|
||||
})?;
|
||||
|
||||
create_dir_all(HUB_CACHE_DIR)?;
|
||||
create_dir_all(BUN_BUNDLE_CACHE_DIR)?;
|
||||
create_dir_all(&*HUB_CACHE_DIR)?;
|
||||
create_dir_all(&*BUN_BUNDLE_CACHE_DIR)?;
|
||||
|
||||
for path in paths.values() {
|
||||
tracing::info!("Caching hub script at {path}");
|
||||
@@ -249,7 +249,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
.as_ref()
|
||||
.is_some_and(|x| x == &ScriptLang::Deno)
|
||||
{
|
||||
let job_dir = format!("{}/cache_init/{}", TMP_DIR, Uuid::new_v4());
|
||||
let job_dir = format!("{}/cache_init/{}", *WINDMILL_DIR, Uuid::new_v4());
|
||||
create_dir_all(&job_dir)?;
|
||||
let _ = windmill_worker::generate_deno_lock(
|
||||
&Uuid::nil(),
|
||||
@@ -267,7 +267,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
tokio::fs::remove_dir_all(job_dir).await?;
|
||||
} else if res.language.as_ref().is_some_and(|x| x == &ScriptLang::Bun) {
|
||||
let job_id = Uuid::new_v4();
|
||||
let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id);
|
||||
let job_dir = format!("{}/cache_init/{}", *WINDMILL_DIR, job_id);
|
||||
create_dir_all(&job_dir)?;
|
||||
if let Some(lock) = res.lockfile {
|
||||
let _ = windmill_worker::prepare_job_dir(&lock, &job_dir).await?;
|
||||
@@ -384,9 +384,9 @@ async fn cache_hub_resource_types() -> anyhow::Result<()> {
|
||||
|
||||
println!("Fetched {} resource types from hub", resource_types.len());
|
||||
|
||||
create_dir_all(HUB_RT_CACHE_DIR)?;
|
||||
create_dir_all(&*HUB_RT_CACHE_DIR)?;
|
||||
|
||||
let cache_path = format!("{}/{}", HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE);
|
||||
let cache_path = format!("{}/{}", *HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE);
|
||||
let content = serde_json::to_string_pretty(&resource_types)
|
||||
.with_context(|| "Failed to serialize resource types")?;
|
||||
|
||||
@@ -398,7 +398,7 @@ async fn cache_hub_resource_types() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn sync_cached_resource_types(db: &sqlx::Pool<sqlx::Postgres>) -> anyhow::Result<()> {
|
||||
let cache_path = format!("{}/{}", HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE);
|
||||
let cache_path = format!("{}/{}", *HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE);
|
||||
|
||||
if tokio::fs::metadata(&cache_path).await.is_err() {
|
||||
tracing::info!(
|
||||
@@ -969,7 +969,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create("/tmp/windmill")
|
||||
.create(&*WINDMILL_DIR)
|
||||
.expect("could not create initial server dir");
|
||||
|
||||
#[cfg(feature = "tantivy")]
|
||||
@@ -1794,27 +1794,27 @@ pub async fn run_workers(
|
||||
let mut handles = Vec::with_capacity(num_workers as usize);
|
||||
|
||||
for x in [
|
||||
TMP_LOGS_DIR,
|
||||
UV_CACHE_DIR,
|
||||
DENO_CACHE_DIR,
|
||||
DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM,
|
||||
BUN_CACHE_DIR,
|
||||
PY310_CACHE_DIR,
|
||||
PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR,
|
||||
PY313_CACHE_DIR,
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
RUST_CACHE_DIR,
|
||||
CSHARP_CACHE_DIR,
|
||||
NU_CACHE_DIR,
|
||||
HUB_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR,
|
||||
JAVA_CACHE_DIR,
|
||||
RUBY_CACHE_DIR,
|
||||
TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG
|
||||
&*TMP_LOGS_DIR,
|
||||
&*UV_CACHE_DIR,
|
||||
&*DENO_CACHE_DIR,
|
||||
&*DENO_CACHE_DIR_DEPS,
|
||||
&*DENO_CACHE_DIR_NPM,
|
||||
&*BUN_CACHE_DIR,
|
||||
&*PY310_CACHE_DIR,
|
||||
&*PY311_CACHE_DIR,
|
||||
&*PY312_CACHE_DIR,
|
||||
&*PY313_CACHE_DIR,
|
||||
&*BUN_BUNDLE_CACHE_DIR,
|
||||
&*GO_CACHE_DIR,
|
||||
&*GO_BIN_CACHE_DIR,
|
||||
&*RUST_CACHE_DIR,
|
||||
&*CSHARP_CACHE_DIR,
|
||||
&*NU_CACHE_DIR,
|
||||
&*HUB_CACHE_DIR,
|
||||
&*POWERSHELL_CACHE_DIR,
|
||||
&*JAVA_CACHE_DIR,
|
||||
&*RUBY_CACHE_DIR,
|
||||
&*TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG
|
||||
] {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
|
||||
@@ -73,7 +73,7 @@ use windmill_common::{
|
||||
load_periodic_bash_script_interval_from_env, load_whitelist_env_vars_from_env,
|
||||
load_worker_config, reload_custom_tags_setting, store_pull_query,
|
||||
store_suspended_pull_query, Connection, WorkerConfig, DEFAULT_TAGS_PER_WORKSPACE,
|
||||
DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR,
|
||||
DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, WINDMILL_DIR,
|
||||
WORKER_CONFIG, WORKER_GROUP,
|
||||
},
|
||||
KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERT_MUTE_UI_ENABLED,
|
||||
@@ -595,7 +595,7 @@ async fn sleep_until_next_minute_start_plus_one_s() {
|
||||
|
||||
use windmill_common::tracing_init::TMP_WINDMILL_LOGS_SERVICE;
|
||||
async fn find_two_highest_files(hostname: &str) -> (Option<String>, Option<String>) {
|
||||
let log_dir = format!("{}/{}/", TMP_WINDMILL_LOGS_SERVICE, hostname);
|
||||
let log_dir = format!("{}/{}/", *TMP_WINDMILL_LOGS_SERVICE, hostname);
|
||||
let rd_dir = tokio::fs::read_dir(log_dir).await;
|
||||
if let Ok(mut log_files) = rd_dir {
|
||||
let mut highest_file: Option<String> = None;
|
||||
@@ -614,7 +614,8 @@ async fn find_two_highest_files(hostname: &str) -> (Option<String>, Option<Strin
|
||||
(highest_file, second_highest_file)
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Error reading log files: {TMP_WINDMILL_LOGS_SERVICE}, {:#?}",
|
||||
"Error reading log files: {}, {:#?}",
|
||||
*TMP_WINDMILL_LOGS_SERVICE,
|
||||
rd_dir.unwrap_err()
|
||||
);
|
||||
(None, None)
|
||||
@@ -716,7 +717,7 @@ async fn send_log_file_to_object_store(
|
||||
let s3_client = windmill_object_store::get_object_store().await;
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = std::path::Path::new(TMP_WINDMILL_LOGS_SERVICE)
|
||||
let path = std::path::Path::new(&*TMP_WINDMILL_LOGS_SERVICE)
|
||||
.join(hostname)
|
||||
.join(&highest_file);
|
||||
|
||||
@@ -935,7 +936,7 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
.iter()
|
||||
.map(|f| format!("{}/{}", f.hostname, f.file_path))
|
||||
.collect();
|
||||
delete_log_files_from_disk_and_store(paths, TMP_WINDMILL_LOGS_SERVICE, windmill_common::tracing_init::LOGS_SERVICE).await;
|
||||
delete_log_files_from_disk_and_store(paths, &*TMP_WINDMILL_LOGS_SERVICE, windmill_common::tracing_init::LOGS_SERVICE).await;
|
||||
|
||||
}
|
||||
Err(e) => tracing::error!("Error deleting log file: {:?}", e),
|
||||
@@ -1140,7 +1141,7 @@ async fn delete_expired_jobs_batch(
|
||||
.filter_map(|opt| opt)
|
||||
.flat_map(|inner_vec| inner_vec.into_iter())
|
||||
.collect();
|
||||
delete_log_files_from_disk_and_store(paths, TMP_DIR, "").await;
|
||||
delete_log_files_from_disk_and_store(paths, &*WINDMILL_DIR, "").await;
|
||||
}
|
||||
Err(e) => tracing::error!("Error deleting job logs: {:?}", e),
|
||||
}
|
||||
@@ -1367,7 +1368,7 @@ pub async fn reload_maven_settings_xml_setting(conn: &Connection) {
|
||||
let settings_xml = MAVEN_SETTINGS_XML.read().await.clone();
|
||||
match settings_xml {
|
||||
Some(ref content) if !content.trim().is_empty() => {
|
||||
let m2_dir = format!("{JAVA_HOME_DIR}/.m2");
|
||||
let m2_dir = format!("{}/.m2", *JAVA_HOME_DIR);
|
||||
if let Err(e) = tokio::fs::create_dir_all(&m2_dir).await {
|
||||
tracing::error!("Failed to create .m2 directory: {e:#}");
|
||||
return;
|
||||
@@ -1378,7 +1379,7 @@ pub async fn reload_maven_settings_xml_setting(conn: &Connection) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let settings_path = format!("{JAVA_HOME_DIR}/.m2/settings.xml");
|
||||
let settings_path = format!("{}/.m2/settings.xml", *JAVA_HOME_DIR);
|
||||
let _ = tokio::fs::remove_file(&settings_path).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
//! Tests for WM_END_USER_EMAIL environment variable.
|
||||
//!
|
||||
//! These tests verify that WM_END_USER_EMAIL is populated with the authenticated
|
||||
//! user's email when executing app components.
|
||||
//!
|
||||
//! TODO: Add tests for scripts and flows once public execution endpoints are identified.
|
||||
//! Currently only apps support non-workspace-member execution via OptAuthed + token lookup.
|
||||
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::worker::Connection;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
const SAME_WS_TOKEN: &str = "SECRET_TOKEN";
|
||||
const OTHER_WS_TOKEN: &str = "OTHER_WS_TOKEN";
|
||||
const NO_WS_TOKEN: &str = "NO_WS_TOKEN";
|
||||
|
||||
const SAME_WS_EMAIL: &str = "test@windmill.dev";
|
||||
const OTHER_WS_EMAIL: &str = "other-ws@windmill.dev";
|
||||
const NO_WS_EMAIL: &str = "no-ws@windmill.dev";
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", format!("Bearer {}", token))
|
||||
}
|
||||
|
||||
// TODO: Script tests - need to identify public execution endpoints for non-workspace-members
|
||||
// async fn run_script(port: u16, token: &str) -> anyhow::Result<String> {
|
||||
// let url = format!(
|
||||
// "http://localhost:{}/api/w/test-workspace/jobs/run_wait_result/p/f/test/get_end_user_email",
|
||||
// port
|
||||
// );
|
||||
// let resp = authed(client().post(&url), token)
|
||||
// .json(&json!({}))
|
||||
// .send()
|
||||
// .await?;
|
||||
// if !resp.status().is_success() {
|
||||
// anyhow::bail!("script run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
// }
|
||||
// Ok(resp.json::<serde_json::Value>().await?
|
||||
// .as_str().unwrap_or("").to_string())
|
||||
// }
|
||||
|
||||
// TODO: Flow tests - need to identify public execution endpoints for non-workspace-members
|
||||
// async fn run_flow(port: u16, token: &str) -> anyhow::Result<String> {
|
||||
// let url = format!(
|
||||
// "http://localhost:{}/api/w/test-workspace/jobs/run_wait_result/f/f/test/get_end_user_email_flow",
|
||||
// port
|
||||
// );
|
||||
// let resp = authed(client().post(&url), token)
|
||||
// .json(&json!({}))
|
||||
// .send()
|
||||
// .await?;
|
||||
// if !resp.status().is_success() {
|
||||
// anyhow::bail!("flow run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
// }
|
||||
// Ok(resp.json::<serde_json::Value>().await?
|
||||
// .as_str().unwrap_or("").to_string())
|
||||
// }
|
||||
|
||||
/// Create an app with inline script via API
|
||||
async fn create_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps/create",
|
||||
port
|
||||
);
|
||||
let resp = authed(client().post(&url), SAME_WS_TOKEN)
|
||||
.json(&json!({
|
||||
"path": path,
|
||||
"summary": "Test app for WM_END_USER_EMAIL",
|
||||
"value": {
|
||||
"type": "app",
|
||||
"grid": [],
|
||||
"subgrids": {},
|
||||
"hiddenInlineScripts": [{
|
||||
"name": "get_email",
|
||||
"language": "deno",
|
||||
"content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }",
|
||||
"path": "f/test/email_app/get_email"
|
||||
}]
|
||||
},
|
||||
"policy": {
|
||||
"execution_mode": "anonymous",
|
||||
"on_behalf_of": null,
|
||||
"on_behalf_of_email": null,
|
||||
"triggerables_v2": {
|
||||
"get_email": {
|
||||
"static_inputs": {},
|
||||
"one_of_inputs": {}
|
||||
},
|
||||
// SHA256 hash of raw_code content for anonymous execution
|
||||
"rawscript/6428aba5aa2d3ea8e1215bfdccbedd3718b18da7a239e3778a9787bb9a0ea606": {
|
||||
"static_inputs": {},
|
||||
"one_of_inputs": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("create app failed: {} - {}", resp.status(), resp.text().await?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a raw app with inline script via API (uses regular app endpoint with rawapp type)
|
||||
async fn create_raw_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps/create",
|
||||
port
|
||||
);
|
||||
let resp = authed(client().post(&url), SAME_WS_TOKEN)
|
||||
.json(&json!({
|
||||
"path": path,
|
||||
"summary": "Test raw app for WM_END_USER_EMAIL",
|
||||
"value": {
|
||||
"type": "rawapp",
|
||||
"css": "",
|
||||
"inlineScripts": [{
|
||||
"name": "get_email",
|
||||
"language": "deno",
|
||||
"content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }"
|
||||
}]
|
||||
},
|
||||
"policy": {
|
||||
"execution_mode": "anonymous",
|
||||
"on_behalf_of": null,
|
||||
"on_behalf_of_email": null,
|
||||
"triggerables_v2": {
|
||||
"get_email": {
|
||||
"static_inputs": {},
|
||||
"one_of_inputs": {}
|
||||
},
|
||||
// SHA256 hash of raw_code content for anonymous execution
|
||||
"rawscript/6428aba5aa2d3ea8e1215bfdccbedd3718b18da7a239e3778a9787bb9a0ea606": {
|
||||
"static_inputs": {},
|
||||
"one_of_inputs": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("create raw app failed: {} - {}", resp.status(), resp.text().await?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result<String> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}",
|
||||
port, app_path
|
||||
);
|
||||
let mut payload = json!({
|
||||
"args": {},
|
||||
"component": "get_email",
|
||||
"raw_code": {
|
||||
"language": "deno",
|
||||
"content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }",
|
||||
"path": format!("{}/get_email", app_path)
|
||||
}
|
||||
});
|
||||
if force_viewer {
|
||||
payload["force_viewer_static_fields"] = json!({});
|
||||
}
|
||||
let resp = authed(client().post(&url), token)
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("app inline script run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
}
|
||||
let job_id = resp.text().await?;
|
||||
wait_for_job_result(port, token, &job_id).await
|
||||
}
|
||||
|
||||
async fn run_raw_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result<String> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}",
|
||||
port, app_path
|
||||
);
|
||||
let mut payload = json!({
|
||||
"args": {},
|
||||
"component": "get_email",
|
||||
"raw_code": {
|
||||
"language": "deno",
|
||||
"content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }"
|
||||
}
|
||||
});
|
||||
if force_viewer {
|
||||
payload["force_viewer_static_fields"] = json!({});
|
||||
}
|
||||
let resp = authed(client().post(&url), token)
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("raw app inline script run failed: {} - {}", resp.status(), resp.text().await?);
|
||||
}
|
||||
let job_id = resp.text().await?;
|
||||
wait_for_job_result(port, token, &job_id).await
|
||||
}
|
||||
|
||||
async fn wait_for_job_result(port: u16, token: &str, job_id: &str) -> anyhow::Result<String> {
|
||||
let url = format!(
|
||||
"http://localhost:{}/api/w/test-workspace/jobs_u/completed/get_result/{}",
|
||||
port, job_id
|
||||
);
|
||||
for _ in 0..100 {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
let resp = authed(client().get(&url), token).send().await?;
|
||||
if resp.status().is_success() {
|
||||
return Ok(resp.json::<serde_json::Value>().await?
|
||||
.as_str().unwrap_or("").to_string());
|
||||
}
|
||||
}
|
||||
anyhow::bail!("timeout waiting for job result")
|
||||
}
|
||||
|
||||
// TODO: Script tests - need to identify public execution endpoints for non-workspace-members
|
||||
// #[cfg(feature = "deno_core")]
|
||||
// #[sqlx::test(fixtures("base", "end_user_email"))]
|
||||
// async fn test_script_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// initialize_tracing().await;
|
||||
// set_jwt_secret().await;
|
||||
// let server = ApiServer::start(db.clone()).await?;
|
||||
// let port = server.addr.port();
|
||||
//
|
||||
// in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// let result = run_script(port, SAME_WS_TOKEN).await?;
|
||||
// assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
// Ok::<(), anyhow::Error>(())
|
||||
// }, port).await?;
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// TODO: Flow tests - need to identify public execution endpoints for non-workspace-members
|
||||
// #[cfg(feature = "deno_core")]
|
||||
// #[sqlx::test(fixtures("base", "end_user_email"))]
|
||||
// async fn test_flow_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// initialize_tracing().await;
|
||||
// set_jwt_secret().await;
|
||||
// let server = ApiServer::start(db.clone()).await?;
|
||||
// let port = server.addr.port();
|
||||
//
|
||||
// in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// let result = run_flow(port, SAME_WS_TOKEN).await?;
|
||||
// assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
// Ok::<(), anyhow::Error>(())
|
||||
// }, port).await?;
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "end_user_email"))]
|
||||
async fn test_app_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
set_jwt_secret().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let app_path = "f/test/email_app";
|
||||
|
||||
in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// Create the app with inline script first
|
||||
create_app_with_inline_script(port, app_path).await?;
|
||||
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email");
|
||||
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email");
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}, port).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "end_user_email"))]
|
||||
async fn test_raw_app_wm_end_user_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
set_jwt_secret().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let app_path = "f/test/email_raw_app";
|
||||
|
||||
in_test_worker(Connection::Sql(db.clone()), async move {
|
||||
// Create the raw app with inline script first
|
||||
create_raw_app_with_inline_script(port, app_path).await?;
|
||||
|
||||
// Same workspace user (force_viewer mode works for workspace members)
|
||||
let result = run_raw_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?;
|
||||
assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email");
|
||||
|
||||
// Other workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email");
|
||||
|
||||
// No workspace user (uses app's anonymous policy + token lookup)
|
||||
let result = run_raw_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?;
|
||||
assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email");
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
}, port).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
-- Fixture for WM_END_USER_EMAIL tests
|
||||
-- Sets up 3 users with different workspace memberships:
|
||||
-- 1. test@windmill.dev - in test-workspace (from base.sql)
|
||||
-- 2. other-ws@windmill.dev - in other-workspace only
|
||||
-- 3. no-ws@windmill.dev - not in any workspace
|
||||
|
||||
-- Second workspace for cross-workspace user
|
||||
INSERT INTO workspace (id, name, owner)
|
||||
VALUES ('other-workspace', 'other-workspace', 'other-ws-user');
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key)
|
||||
VALUES ('other-workspace', 'cloud', 'other-key');
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id)
|
||||
VALUES ('other-workspace');
|
||||
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('other-workspace', 'all', 'All users', '{}');
|
||||
|
||||
-- User in other-workspace only (not in test-workspace)
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('other-ws@windmill.dev', 'hash', 'password', false, true, 'Other WS User');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role)
|
||||
VALUES ('other-workspace', 'other-ws@windmill.dev', 'other-ws-user', true, 'Admin');
|
||||
|
||||
INSERT INTO token(token, email, label, super_admin)
|
||||
VALUES ('OTHER_WS_TOKEN', 'other-ws@windmill.dev', 'other ws token', false);
|
||||
|
||||
-- User not in any workspace
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('no-ws@windmill.dev', 'hash', 'password', false, true, 'No WS User');
|
||||
|
||||
INSERT INTO token(token, email, label, super_admin)
|
||||
VALUES ('NO_WS_TOKEN', 'no-ws@windmill.dev', 'no ws token', false);
|
||||
|
||||
-- Script that returns WM_END_USER_EMAIL (public via extra_perms)
|
||||
INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, extra_perms)
|
||||
VALUES (
|
||||
'test-workspace', 'test-user',
|
||||
'export function main() { return Deno.env.get("WM_END_USER_EMAIL") || ""; }',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'Returns WM_END_USER_EMAIL', '', 'f/test/get_end_user_email', 900001, 'deno', '', 'script',
|
||||
'{"g/all": true}'
|
||||
);
|
||||
|
||||
-- Flow that returns WM_END_USER_EMAIL (public via extra_perms)
|
||||
INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by, extra_perms)
|
||||
VALUES (
|
||||
'test-workspace', 'Returns WM_END_USER_EMAIL', '', 'f/test/get_end_user_email_flow', '{900002}',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"type": "rawscript", "language": "deno", "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", "input_transforms": {}}}]}',
|
||||
'test-user',
|
||||
'{"g/all": true}'
|
||||
);
|
||||
|
||||
INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by)
|
||||
VALUES (
|
||||
900002, 'test-workspace', 'f/test/get_end_user_email_flow',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"type": "rawscript", "language": "deno", "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", "input_transforms": {}}}]}',
|
||||
'test-user'
|
||||
);
|
||||
@@ -206,7 +206,7 @@ fn spawn_workers(
|
||||
|
||||
std::fs::DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(windmill_worker::GO_BIN_CACHE_DIR)
|
||||
.create(&*windmill_worker::GO_BIN_CACHE_DIR)
|
||||
.expect("could not create initial worker dir");
|
||||
|
||||
let (tx, _) = KillpillSender::new(n + 1);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use windmill_test_utils::*;
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx::Pool;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base", "lockfile_python"))]
|
||||
@@ -188,7 +188,8 @@ def main():
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
@@ -207,14 +208,14 @@ def main():
|
||||
#[cfg(feature = "python")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_python_global_site_packages(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use windmill_common::{cache::concatcp, worker::ROOT_CACHE_DIR};
|
||||
use windmill_common::worker::ROOT_CACHE_DIR;
|
||||
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Shared for all 3.12.*
|
||||
let path = concatcp!(ROOT_CACHE_DIR, "python_3_12/global-site-packages").to_owned();
|
||||
let path = format!("{}python_3_12/global-site-packages", *ROOT_CACHE_DIR);
|
||||
std::fs::create_dir_all(&path).unwrap();
|
||||
std::fs::write(path + "/my_global_site_package_3_12_any.py", "").unwrap();
|
||||
|
||||
@@ -237,7 +238,9 @@ def main():
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default(
|
||||
)
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
@@ -271,7 +274,9 @@ def main():
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default(
|
||||
)
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
@@ -310,7 +315,8 @@ def main():
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
@@ -347,7 +353,8 @@ def main():
|
||||
path: None,
|
||||
language: ScriptLang::Python3,
|
||||
lock: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
|
||||
@@ -35,7 +35,45 @@ use windmill_common::{
|
||||
lazy_static::lazy_static! {
|
||||
// Global auth cache accessible from main.rs for direct invalidation
|
||||
pub static ref AUTH_CACHE: Cache<(String, String), ExpiringAuthCache> = Cache::new(300);
|
||||
// Cache for token -> email lookups (for non-workspace-member authenticated users)
|
||||
static ref TOKEN_EMAIL_CACHE: Cache<String, Option<String>> = Cache::new(500);
|
||||
}
|
||||
|
||||
/// Get email from a valid token, with caching.
|
||||
/// Used for WM_END_USER_EMAIL when user is authenticated but not a workspace member.
|
||||
async fn get_email_from_token(db: &DB, token: &str) -> Option<String> {
|
||||
if let Some(cached) = TOKEN_EMAIL_CACHE.get(token) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM token WHERE token = $1 AND (expiration > NOW() OR expiration IS NULL)",
|
||||
token
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.flatten(); // email column is nullable, so we get Option<Option<String>>
|
||||
|
||||
TOKEN_EMAIL_CACHE.insert(token.to_string(), email.clone());
|
||||
email
|
||||
}
|
||||
|
||||
/// Get end user email from authenticated user or token.
|
||||
/// Returns email if user is authenticated (workspace member) or has valid instance token.
|
||||
pub async fn get_end_user_email(
|
||||
db: &DB,
|
||||
opt_authed: Option<&ApiAuthed>,
|
||||
token: Option<&str>,
|
||||
) -> Option<String> {
|
||||
if let Some(authed) = opt_authed {
|
||||
return Some(authed.email.clone());
|
||||
}
|
||||
if let Some(token) = token {
|
||||
return get_email_from_token(db, token).await;
|
||||
}
|
||||
None
|
||||
}
|
||||
// Global function to invalidate a specific token from cache
|
||||
pub fn invalidate_token_from_cache(token: &str) {
|
||||
|
||||
@@ -29,8 +29,8 @@ use scopes::ScopeDefinition;
|
||||
|
||||
// Re-export key auth types and functions
|
||||
pub use auth::{
|
||||
invalidate_token_from_cache, AuthCache, ExpiringAuthCache, OptTokened, Tokened,
|
||||
TruncatedTokenWithEmail, AUTH_CACHE,
|
||||
get_end_user_email, invalidate_token_from_cache, AuthCache, ExpiringAuthCache, OptTokened,
|
||||
Tokened, TruncatedTokenWithEmail, AUTH_CACHE,
|
||||
};
|
||||
|
||||
// ------------ ApiAuthed & OptJobAuthed types ------------
|
||||
|
||||
@@ -1085,7 +1085,7 @@ async fn sync_cached_resource_types(
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
use windmill_common::worker::HUB_RT_CACHE_DIR;
|
||||
let cache_path = format!("{}/resource_types.json", HUB_RT_CACHE_DIR);
|
||||
let cache_path = format!("{}/resource_types.json", *HUB_RT_CACHE_DIR);
|
||||
|
||||
let content = tokio::fs::read_to_string(&cache_path).await.map_err(|e| {
|
||||
error::Error::NotFound(format!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.648.0
|
||||
version: 1.649.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
use crate::{
|
||||
auth::OptTokened,
|
||||
auth::{get_end_user_email, OptTokened},
|
||||
db::{ApiAuthed, DB},
|
||||
jobs::RunJobQuery,
|
||||
users::{require_owner_of_path, OptAuthed},
|
||||
@@ -2149,7 +2149,7 @@ async fn execute_component(
|
||||
(email.as_str(), permissioned_as)
|
||||
};
|
||||
|
||||
let end_user_email = opt_authed.as_ref().map(|a| a.email.clone());
|
||||
let end_user_email = get_end_user_email(&db, opt_authed.as_ref(), tokened.token.as_deref()).await;
|
||||
|
||||
let (uuid, mut tx) = push(
|
||||
&db,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use windmill_api_auth::auth::{
|
||||
invalidate_token_from_cache, list_tokens_internal, transform_old_scope_to_new_scope, AuthCache,
|
||||
ExpiringAuthCache, OptTokened, Tokened, TruncatedTokenWithEmail,
|
||||
get_end_user_email, invalidate_token_from_cache, list_tokens_internal,
|
||||
transform_old_scope_to_new_scope, AuthCache, ExpiringAuthCache, OptTokened, Tokened,
|
||||
TruncatedTokenWithEmail,
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAsset
|
||||
use windmill_common::scripts::ScriptRunnableSettingsInline;
|
||||
use windmill_common::triggers::TriggerMetadata;
|
||||
use windmill_common::utils::{RunnableKind, WarnAfterExt};
|
||||
use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR};
|
||||
use windmill_common::worker::{Connection, CLOUD_HOSTED, WINDMILL_DIR};
|
||||
use windmill_common::workspace_dependencies::{
|
||||
RawWorkspaceDependencies, MIN_VERSION_WORKSPACE_DEPENDENCIES,
|
||||
};
|
||||
@@ -1412,7 +1412,7 @@ async fn get_logs_from_disk(
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
for file_p in &file_index {
|
||||
if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}"))
|
||||
if !tokio::fs::metadata(format!("{}/{file_p}", *WINDMILL_DIR))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -1427,7 +1427,7 @@ async fn get_logs_from_disk(
|
||||
"#.to_string(),
|
||||
));
|
||||
for file_p in file_index.clone() {
|
||||
let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?;
|
||||
let mut file = tokio::fs::File::open(format!("{}/{file_p}", *WINDMILL_DIR)).await.map_err(to_anyhow)?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await.map_err(to_anyhow)?;
|
||||
yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result<bytes::Bytes>;
|
||||
@@ -5888,7 +5888,7 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
|
||||
));
|
||||
}
|
||||
|
||||
let local_file = format!("{TMP_DIR}/logs/{file_p}");
|
||||
let local_file = format!("{}/logs/{file_p}", *WINDMILL_DIR);
|
||||
if tokio::fs::metadata(&local_file).await.is_ok() {
|
||||
let mut file = tokio::fs::File::open(local_file).await.map_err(to_anyhow)?;
|
||||
let mut buffer = Vec::new();
|
||||
@@ -5934,10 +5934,10 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
return Err(error::Error::NotFound(format!(
|
||||
"File not found on server logs volume /tmp/windmill/logs and no distributed logs s3 storage for {}",
|
||||
file_p
|
||||
)));
|
||||
return Err(error::Error::NotFound(format!(
|
||||
"File not found on server logs volume {}/logs and no distributed logs s3 storage for {}",
|
||||
*WINDMILL_DIR, file_p
|
||||
)));
|
||||
}
|
||||
|
||||
async fn get_job_update(
|
||||
|
||||
@@ -262,7 +262,7 @@ pub async fn run_server(
|
||||
) -> anyhow::Result<()> {
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
for x in [HUB_CACHE_DIR] {
|
||||
for x in [&*HUB_CACHE_DIR] {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(x)
|
||||
|
||||
@@ -102,7 +102,11 @@ async fn get_log_file(
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = format!("{}{}", windmill_common::tracing_init::LOGS_SERVICE, path);
|
||||
let file = s3_client.get(&windmill_object_store::object_store_reexports::Path::from(path)).await;
|
||||
let file = s3_client
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(
|
||||
path,
|
||||
))
|
||||
.await;
|
||||
match file {
|
||||
Ok(file) => {
|
||||
let bytes = file.bytes().await;
|
||||
@@ -126,7 +130,7 @@ async fn get_log_file(
|
||||
}
|
||||
}
|
||||
}
|
||||
let file = tokio::fs::read(format!("{}{}", TMP_WINDMILL_LOGS_SERVICE, path)).await;
|
||||
let file = tokio::fs::read(format!("{}{}", *TMP_WINDMILL_LOGS_SERVICE, path)).await;
|
||||
if let Ok(bytes) = file {
|
||||
Ok(content_plain(Body::from(bytes::Bytes::from(bytes))))
|
||||
} else {
|
||||
|
||||
@@ -43,6 +43,7 @@ use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings
|
||||
use windmill_common::scripts::ScriptRunnableSettingsHandle;
|
||||
use windmill_common::utils::require_admin;
|
||||
use windmill_common::variables::decrypt;
|
||||
use windmill_common::worker::WINDMILL_DIR;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{to_anyhow, Error, Result},
|
||||
@@ -372,7 +373,7 @@ pub(crate) async fn tarball_workspace(
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let tmp_dir = TempDir::new_in("/tmp/windmill/")?;
|
||||
let tmp_dir = TempDir::new_in(&*WINDMILL_DIR)?;
|
||||
|
||||
let name = match archive_type.as_deref() {
|
||||
Some("tar") | None => Ok(format!("windmill-{w_id}.tar")),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
worker::{write_file, TMP_DIR},
|
||||
worker::{write_file, WINDMILL_DIR},
|
||||
DB,
|
||||
};
|
||||
use serde::Serialize;
|
||||
@@ -113,7 +113,8 @@ impl BenchmarkInfo {
|
||||
"Writing benchmark {path}, duration of benchmark: {total_duration}ms and RPS: {}{pool_info}",
|
||||
self.iters as f64 / total_duration as f64 * 1000.0
|
||||
);
|
||||
write_file(TMP_DIR, path, &serde_json::to_string(&self).unwrap()).expect("write profiling");
|
||||
write_file(&WINDMILL_DIR, path, &serde_json::to_string(&self).unwrap())
|
||||
.expect("write profiling");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{StripPath, HTTP_CLIENT},
|
||||
worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, TMP_DIR},
|
||||
worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, WINDMILL_DIR},
|
||||
FlowVersionInfo, ScriptHashInfo, Tag,
|
||||
};
|
||||
|
||||
@@ -225,7 +225,7 @@ pub async fn get_logs_from_disk(
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
for file_p in &file_index {
|
||||
if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}"))
|
||||
if !tokio::fs::metadata(format!("{}/{file_p}", *WINDMILL_DIR))
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
@@ -236,7 +236,7 @@ pub async fn get_logs_from_disk(
|
||||
let logs = logs.to_string();
|
||||
let stream = async_stream::stream! {
|
||||
for file_p in file_index.clone() {
|
||||
let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?;
|
||||
let mut file = tokio::fs::File::open(format!("{}/{file_p}", *WINDMILL_DIR)).await.map_err(to_anyhow)?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await.map_err(to_anyhow)?;
|
||||
yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result<bytes::Bytes>;
|
||||
|
||||
@@ -206,12 +206,12 @@ pub async fn get_full_hub_script_by_path(
|
||||
let version = path_iterator
|
||||
.next()
|
||||
.ok_or_else(|| Error::internal_err(format!("expected hub path to have version number")))?;
|
||||
let cache_path = format!("{HUB_CACHE_DIR}/{version}");
|
||||
let cache_path = format!("{}/{version}", *HUB_CACHE_DIR);
|
||||
let script;
|
||||
if tokio::fs::metadata(&cache_path).await.is_err() {
|
||||
script = get_full_hub_script_by_path_inner(path, http_client, db).await?;
|
||||
if let Err(e) = crate::worker::write_file(
|
||||
HUB_CACHE_DIR,
|
||||
&HUB_CACHE_DIR,
|
||||
&version,
|
||||
&serde_json::to_string(&script).map_err(to_anyhow)?,
|
||||
) {
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use const_format::concatcp;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, RwLock},
|
||||
@@ -61,7 +59,9 @@ fn create_targets_filter(default_env_filter: LevelFilter) -> Targets {
|
||||
|
||||
pub const LOGS_SERVICE: &str = "logs/services/";
|
||||
|
||||
pub const TMP_WINDMILL_LOGS_SERVICE: &str = concatcp!("/tmp/windmill/", LOGS_SERVICE);
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TMP_WINDMILL_LOGS_SERVICE: String = format!("{}/{}", *crate::worker::WINDMILL_DIR, LOGS_SERVICE);
|
||||
}
|
||||
|
||||
pub fn initialize_tracing(
|
||||
hostname: &str,
|
||||
@@ -108,7 +108,7 @@ pub fn initialize_tracing(
|
||||
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
|
||||
let log_dir = format!("{}/{}/", TMP_WINDMILL_LOGS_SERVICE, hostname);
|
||||
let log_dir = format!("{}/{}/", *TMP_WINDMILL_LOGS_SERVICE, hostname);
|
||||
std::fs::create_dir_all(&log_dir).unwrap();
|
||||
let file_appender = RollingFileAppender::builder()
|
||||
.rotation(Rotation::MINUTELY)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
use anyhow::anyhow;
|
||||
use axum::http::HeaderMap;
|
||||
use bytes::Bytes;
|
||||
use const_format::concatcp;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
@@ -274,7 +273,9 @@ lazy_static::lazy_static! {
|
||||
pub static ref ROOT_STANDALONE_BUNDLE_DIR: String = format!("{}/.windmill/standalone_bundle", std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()));
|
||||
}
|
||||
|
||||
pub const ROOT_CACHE_NOMOUNT_DIR: &str = concatcp!(TMP_DIR, "/cache_nomount/");
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref ROOT_CACHE_NOMOUNT_DIR: String = format!("{}/cache_nomount/", *WINDMILL_DIR);
|
||||
}
|
||||
|
||||
/// Whether native mode is forced by the environment (NATIVE_MODE=true env var or WORKER_GROUP=native).
|
||||
/// This does NOT account for native_mode set in the DB worker group config — for that, read
|
||||
@@ -490,13 +491,23 @@ pub async fn store_pull_query(wc: &WorkerConfig) {
|
||||
*l = queries;
|
||||
}
|
||||
|
||||
pub const TMP_DIR: &str = "/tmp/windmill";
|
||||
pub const TMP_LOGS_DIR: &str = concatcp!(TMP_DIR, "/logs");
|
||||
|
||||
pub const HUB_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub");
|
||||
pub const HUB_RT_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub_rt");
|
||||
|
||||
pub const ROOT_CACHE_DIR: &str = concatcp!(TMP_DIR, "/cache/");
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref WINDMILL_DIR: String = {
|
||||
let dir = std::env::var("WINDMILL_DIR")
|
||||
.unwrap_or_else(|_| "/tmp/windmill".to_string());
|
||||
if dir.is_empty() {
|
||||
panic!("WINDMILL_DIR must not be empty");
|
||||
}
|
||||
if dir.ends_with('/') || dir.ends_with('\\') {
|
||||
panic!("WINDMILL_DIR must not end with a trailing slash, got: {dir}");
|
||||
}
|
||||
dir
|
||||
};
|
||||
pub static ref TMP_LOGS_DIR: String = format!("{}/logs", *WINDMILL_DIR);
|
||||
pub static ref ROOT_CACHE_DIR: String = format!("{}/cache/", *WINDMILL_DIR);
|
||||
pub static ref HUB_CACHE_DIR: String = format!("{}hub", *ROOT_CACHE_DIR);
|
||||
pub static ref HUB_RT_CACHE_DIR: String = format!("{}hub_rt", *ROOT_CACHE_DIR);
|
||||
}
|
||||
|
||||
pub fn write_file(dir: &str, path: &str, content: &str) -> error::Result<File> {
|
||||
let path = format!("{}/{}", dir, path);
|
||||
|
||||
@@ -2164,6 +2164,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"duckdb",
|
||||
"regex",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
chrono = "0.4.41"
|
||||
duckdb = { version = "1.4.4", features = ["bundled"] }
|
||||
regex = "1"
|
||||
rust_decimal = "1.37.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CStr, CString, c_char, c_uint},
|
||||
ffi::{c_char, c_uint, CStr, CString},
|
||||
ptr::null_mut,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use duckdb::{Row, core::LogicalTypeId, params_from_iter, types::TimeUnit};
|
||||
use rust_decimal::{Decimal, prelude::FromPrimitive};
|
||||
use serde::Deserialize;
|
||||
use duckdb::{core::LogicalTypeId, params_from_iter, types::TimeUnit, Row};
|
||||
use regex::Regex;
|
||||
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq, Default)]
|
||||
@@ -96,6 +98,218 @@ pub extern "C" fn run_duckdb_ffi(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct PrepareQueryColumnInfo {
|
||||
name: String,
|
||||
#[serde(rename = "type")]
|
||||
type_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct PrepareQueryResult {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
columns: Option<Vec<PrepareQueryColumnInfo>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
fn is_setup_statement(query: &str) -> bool {
|
||||
let trimmed = query.trim_start();
|
||||
let upper = trimmed.to_uppercase();
|
||||
upper.starts_with("ATTACH")
|
||||
|| upper.starts_with("USE")
|
||||
|| upper.starts_with("INSTALL")
|
||||
|| upper.starts_with("LOAD")
|
||||
|| upper.starts_with("SET")
|
||||
|| upper.starts_with("RESET")
|
||||
|| upper.starts_with("CREATE OR REPLACE SECRET")
|
||||
|| upper.starts_with("CREATE SECRET")
|
||||
}
|
||||
|
||||
/// Returns true if the query is expected to return a result set and can be wrapped with DESCRIBE.
|
||||
fn is_describable_query(query: &str) -> bool {
|
||||
let trimmed = query.trim_start();
|
||||
let upper = trimmed.to_uppercase();
|
||||
upper.starts_with("SELECT")
|
||||
|| upper.starts_with("WITH")
|
||||
|| upper.starts_with("VALUES")
|
||||
|| upper.starts_with("TABLE")
|
||||
|| upper.starts_with("FROM")
|
||||
}
|
||||
|
||||
static PARAM_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$\d+").expect("invalid regex"));
|
||||
|
||||
fn replace_params_with_null(query: &str) -> String {
|
||||
PARAM_RE.replace_all(query, "NULL").to_string()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn prepare_duckdb_ffi(
|
||||
query_block_list: *const *const c_char,
|
||||
query_block_list_count: usize,
|
||||
token: *const c_char,
|
||||
base_internal_url: *const c_char,
|
||||
w_id: *const c_char,
|
||||
) -> *mut c_char {
|
||||
let r = match convert_prepare_args(
|
||||
query_block_list,
|
||||
query_block_list_count,
|
||||
token,
|
||||
base_internal_url,
|
||||
w_id,
|
||||
)
|
||||
.and_then(|(query_block_list, token, base_internal_url, w_id)| {
|
||||
prepare_duckdb_internal(query_block_list, token, base_internal_url, w_id)
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
let err = serde_json::to_string(&err)
|
||||
.unwrap_or_else(|_| "Unknown error in duckdb ffi lib".to_string());
|
||||
format!("ERROR {}", err)
|
||||
}
|
||||
};
|
||||
|
||||
CString::new(r).map(|s| s.into_raw()).unwrap_or_else(|e| {
|
||||
println!("Failed to allocate error string in duckdb ffi lib: {:?}", e);
|
||||
null_mut()
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_duckdb_connection(
|
||||
conn: &duckdb::Connection,
|
||||
token: &str,
|
||||
base_internal_url: &str,
|
||||
w_id: &str,
|
||||
) -> Result<(), String> {
|
||||
let (s3_access_key, s3_secret_key) = token.rsplit_once('.').unwrap_or(("", token));
|
||||
let (s3_endpoint_ssl, s3_endpoint) = base_internal_url
|
||||
.split_once("://")
|
||||
.unwrap_or(("http", &base_internal_url));
|
||||
let s3_endpoint_ssl = s3_endpoint_ssl == "https";
|
||||
|
||||
conn.execute_batch(&format!(
|
||||
"INSTALL httpfs; LOAD httpfs;
|
||||
INSTALL azure; LOAD azure;
|
||||
CREATE OR REPLACE SECRET s3_secret (
|
||||
TYPE s3,
|
||||
PROVIDER config,
|
||||
KEY_ID '{s3_access_key}',
|
||||
SECRET '{s3_secret_key}',
|
||||
ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy',
|
||||
URL_STYLE path,
|
||||
USE_SSL {s3_endpoint_ssl}
|
||||
);
|
||||
CREATE OR REPLACE SECRET gcs_secret (
|
||||
TYPE gcs,
|
||||
KEY_ID '{s3_access_key}',
|
||||
SECRET '{s3_secret_key}',
|
||||
ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy',
|
||||
USE_SSL {s3_endpoint_ssl}
|
||||
);
|
||||
",
|
||||
))
|
||||
.map_err(|e| format!("Error setting up S3 secret: {}", e.to_string()))
|
||||
}
|
||||
|
||||
fn convert_prepare_args<'a>(
|
||||
query_block_list: *const *const c_char,
|
||||
query_block_list_count: usize,
|
||||
token: *const c_char,
|
||||
base_internal_url: *const c_char,
|
||||
w_id: *const c_char,
|
||||
) -> Result<(Vec<&'a str>, &'a str, &'a str, &'a str), String> {
|
||||
let query_block_list = unsafe {
|
||||
std::slice::from_raw_parts(query_block_list, query_block_list_count)
|
||||
.iter()
|
||||
.map(|q| {
|
||||
CStr::from_ptr(*q).to_str().unwrap_or_else(|e| {
|
||||
println!(
|
||||
"Invalid query_block string pointer in duckdb ffi: {}",
|
||||
e.to_string()
|
||||
);
|
||||
"Invalid query_block string pointer in duckdb ffi"
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let token = unsafe { CStr::from_ptr(token) }
|
||||
.to_str()
|
||||
.map_err(|e| format!("Invalid token string: {}", e.to_string()))?;
|
||||
let base_internal_url = unsafe { CStr::from_ptr(base_internal_url) }
|
||||
.to_str()
|
||||
.map_err(|e| format!("Invalid base_internal_url string: {}", e.to_string()))?;
|
||||
let w_id = unsafe { CStr::from_ptr(w_id) }
|
||||
.to_str()
|
||||
.map_err(|e| format!("Invalid w_id string: {}", e.to_string()))?;
|
||||
Ok((query_block_list, token, base_internal_url, w_id))
|
||||
}
|
||||
|
||||
fn prepare_duckdb_internal(
|
||||
query_block_list: Vec<&str>,
|
||||
token: &str,
|
||||
base_internal_url: &str,
|
||||
w_id: &str,
|
||||
) -> Result<String, String> {
|
||||
let conn = duckdb::Connection::open_in_memory().map_err(|e| e.to_string())?;
|
||||
|
||||
setup_duckdb_connection(&conn, token, base_internal_url, w_id)?;
|
||||
|
||||
let mut results: Vec<PrepareQueryResult> = vec![];
|
||||
|
||||
// IMPORTANT: Setup statements (ATTACH, USE, INSTALL, etc.) are executed but intentionally
|
||||
// do not produce a PrepareQueryResult entry. The frontend prepends these as connection setup
|
||||
// before the actual user queries, and mapPrepareResults expects results.length to equal the
|
||||
// number of user queries (not setup statements). If a new setup-like statement is added to
|
||||
// the connection flow (e.g. in setup_duckdb_connection or transform_attach_ducklake) without
|
||||
// also being caught by is_setup_statement, the result count will mismatch and the frontend
|
||||
// will throw.
|
||||
for query_block in &query_block_list {
|
||||
if is_setup_statement(query_block) {
|
||||
conn.execute_batch(query_block)
|
||||
.map_err(|e| format!("Error executing setup statement: {}", e.to_string()))?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let modified_query = replace_params_with_null(query_block);
|
||||
// Validate the query parses correctly by preparing it
|
||||
if let Err(e) = conn.prepare(&modified_query) {
|
||||
results.push(PrepareQueryResult { columns: None, error: Some(e.to_string()) });
|
||||
continue;
|
||||
}
|
||||
|
||||
// DESCRIBE only works on queries that return result sets (SELECT, WITH, VALUES, TABLE,
|
||||
// FROM). For non-returning statements (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.)
|
||||
// we skip DESCRIBE and assume no columns.
|
||||
if !is_describable_query(&modified_query) {
|
||||
results.push(PrepareQueryResult { columns: Some(vec![]), error: None });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: We have to use a DESCRIBE statement and cannot simply use the
|
||||
// methods returned by .prepare() because they panic if the statement was
|
||||
// not executed at least once (which we specifically do not want to do).
|
||||
let describe_query = format!("DESCRIBE {}", modified_query);
|
||||
match conn.prepare(&describe_query).and_then(|mut stmt| {
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(PrepareQueryColumnInfo {
|
||||
name: row.get::<_, String>(0)?,
|
||||
type_name: row.get::<_, String>(1)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
}) {
|
||||
Ok(columns) => {
|
||||
results.push(PrepareQueryResult { columns: Some(columns), error: None });
|
||||
}
|
||||
Err(e) => {
|
||||
results.push(PrepareQueryResult { columns: None, error: Some(e.to_string()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::to_string(&results).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn convert_args<'a>(
|
||||
query_block_list: *const *const c_char,
|
||||
query_block_list_count: usize,
|
||||
@@ -170,38 +384,7 @@ fn run_duckdb_internal<'a>(
|
||||
) -> Result<(String, Option<Vec<String>>), String> {
|
||||
let conn = duckdb::Connection::open_in_memory().map_err(|e| e.to_string())?;
|
||||
|
||||
let (s3_access_key, s3_secret_key) = token.split_at(token.rfind('.').unwrap_or(0));
|
||||
let s3_secret_key = &s3_secret_key[1..];
|
||||
let (s3_endpoint_ssl, s3_endpoint) = base_internal_url
|
||||
.split_once("://")
|
||||
.unwrap_or(("http", &base_internal_url));
|
||||
let s3_endpoint_ssl = match s3_endpoint_ssl {
|
||||
"https" => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
conn.execute_batch(&format!(
|
||||
"INSTALL httpfs; LOAD httpfs;
|
||||
INSTALL azure; LOAD azure;
|
||||
CREATE OR REPLACE SECRET s3_secret (
|
||||
TYPE s3,
|
||||
PROVIDER config,
|
||||
KEY_ID '{s3_access_key}',
|
||||
SECRET '{s3_secret_key}',
|
||||
ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy',
|
||||
URL_STYLE path,
|
||||
USE_SSL {s3_endpoint_ssl}
|
||||
);
|
||||
CREATE OR REPLACE SECRET gcs_secret (
|
||||
TYPE gcs,
|
||||
KEY_ID '{s3_access_key}',
|
||||
SECRET '{s3_secret_key}',
|
||||
ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy',
|
||||
USE_SSL {s3_endpoint_ssl}
|
||||
);
|
||||
",
|
||||
))
|
||||
.map_err(|e| format!("Error setting up S3 secret: {}", e.to_string()))?;
|
||||
setup_duckdb_connection(&conn, token, base_internal_url, w_id)?;
|
||||
|
||||
let mut results: Vec<Vec<Box<RawValue>>> = vec![];
|
||||
let mut column_order = None;
|
||||
|
||||
@@ -45,7 +45,7 @@ use uuid::Uuid;
|
||||
|
||||
use windmill_common::error::Error;
|
||||
use windmill_common::result_stream::append_result_stream_db;
|
||||
use windmill_common::worker::{write_file, Connection, TMP_DIR};
|
||||
use windmill_common::worker::{write_file, Connection, WINDMILL_DIR};
|
||||
|
||||
// ── Permission container ─────────────────────────────────────────────
|
||||
|
||||
@@ -151,7 +151,9 @@ static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/FETCH
|
||||
|
||||
pub(crate) const WINDMILL_CLIENT: &str = include_str!("./windmill-client.js");
|
||||
|
||||
const ERROR_DIR: &str = const_format::concatcp!(TMP_DIR, "/native_errors");
|
||||
lazy_static::lazy_static! {
|
||||
static ref ERROR_DIR: String = format!("{}/native_errors", *WINDMILL_DIR);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_PROXY: Regex =
|
||||
@@ -263,14 +265,14 @@ fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> {
|
||||
}
|
||||
|
||||
fn write_error_expr(expr: &str, uuid: &Uuid) {
|
||||
if let Err(e) = std::fs::create_dir_all(ERROR_DIR) {
|
||||
tracing::error!("failed to create error dir {ERROR_DIR}: {e}");
|
||||
if let Err(e) = std::fs::create_dir_all(&*ERROR_DIR) {
|
||||
tracing::error!("failed to create error dir {}: {e}", *ERROR_DIR);
|
||||
return;
|
||||
}
|
||||
let dir_entries = match std::fs::read_dir(ERROR_DIR) {
|
||||
let dir_entries = match std::fs::read_dir(&*ERROR_DIR) {
|
||||
Ok(entries) => entries.count(),
|
||||
Err(_) => {
|
||||
tracing::error!("failed to read error dir {ERROR_DIR}");
|
||||
tracing::error!("failed to read error dir {}", *ERROR_DIR);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -279,15 +281,16 @@ fn write_error_expr(expr: &str, uuid: &Uuid) {
|
||||
tracing::info!("native error for job {uuid}: {expr}");
|
||||
}
|
||||
if dir_entries >= 100 {
|
||||
tracing::info!("Too many error files in {ERROR_DIR}, skipping write");
|
||||
tracing::info!("Too many error files in {}, skipping write", *ERROR_DIR);
|
||||
return;
|
||||
}
|
||||
|
||||
let path = format!("/{uuid}.js");
|
||||
tracing::info!(
|
||||
"nativets job {uuid} failed, writing error expr to {ERROR_DIR}/{path} for debugging: {path}"
|
||||
"nativets job {uuid} failed, writing error expr to {}/{path} for debugging: {path}",
|
||||
*ERROR_DIR
|
||||
);
|
||||
if let Err(e) = write_file(ERROR_DIR, &path, expr) {
|
||||
if let Err(e) = write_file(&ERROR_DIR, &path, expr) {
|
||||
tracing::error!("failed to write error expr to file {path}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ use windmill_common::{
|
||||
StripPath,
|
||||
},
|
||||
variables,
|
||||
worker::{CLOUD_HOSTED, TMP_DIR},
|
||||
worker::{CLOUD_HOSTED, WINDMILL_DIR},
|
||||
PgDatabase,
|
||||
};
|
||||
|
||||
@@ -1752,7 +1752,7 @@ async fn write_ssh_file(
|
||||
var_path: &str,
|
||||
) -> std::result::Result<std::path::PathBuf, (error::Error, std::path::PathBuf)> {
|
||||
let id_file_name = format!(".ssh_id_priv_{}", Uuid::new_v4());
|
||||
let loc = std::path::Path::new(TMP_DIR)
|
||||
let loc = std::path::Path::new(&*WINDMILL_DIR)
|
||||
.join("ssh_ids")
|
||||
.join(id_file_name);
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ pub fn spawn_test_worker(
|
||||
|
||||
std::fs::DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(windmill_worker::GO_BIN_CACHE_DIR)
|
||||
.create(&*windmill_worker::GO_BIN_CACHE_DIR)
|
||||
.expect("could not create initial worker dir");
|
||||
|
||||
let (tx, rx) = KillpillSender::new(1);
|
||||
|
||||
@@ -127,7 +127,7 @@ iface_no_lo: true
|
||||
|
||||
mount {
|
||||
src: "{CACHE_DIR}"
|
||||
dst: "/tmp/windmill/cache/powershell"
|
||||
dst: "{CACHE_DIR}"
|
||||
is_bind: true
|
||||
rw: false
|
||||
mandatory: false
|
||||
|
||||
@@ -33,9 +33,9 @@ use crate::{
|
||||
read_and_check_result, start_child_process, transform_json, OccupancyMetrics,
|
||||
},
|
||||
handle_child::handle_child,
|
||||
is_sandboxing_enabled,
|
||||
python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile},
|
||||
is_sandboxing_enabled, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
|
||||
PY_INSTALL_DIR, TZ_ENV,
|
||||
DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV,
|
||||
};
|
||||
use windmill_common::client::AuthedClient;
|
||||
|
||||
@@ -1184,7 +1184,7 @@ mount {{
|
||||
job_dir,
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_ANSIBLE_CONTENT
|
||||
.replace("{PY_INSTALL_DIR}", PY_INSTALL_DIR)
|
||||
.replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR)
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
|
||||
@@ -194,7 +194,7 @@ exit $exit_status
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
let mut cmd_args = vec![
|
||||
|
||||
@@ -737,7 +737,7 @@ async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<PulledCode
|
||||
|
||||
let bun_cache_path = format!(
|
||||
"{}/{}.{}",
|
||||
windmill_common::worker::ROOT_CACHE_NOMOUNT_DIR,
|
||||
*windmill_common::worker::ROOT_CACHE_NOMOUNT_DIR,
|
||||
path,
|
||||
if is_tar { "tar" } else { "js" }
|
||||
);
|
||||
@@ -921,7 +921,7 @@ pub async fn compute_bundle_local_and_remote_path(
|
||||
};
|
||||
|
||||
let hash = windmill_common::utils::calculate_hash(&input_src);
|
||||
let local_path = format!("{BUN_BUNDLE_CACHE_DIR}/{hash}");
|
||||
let local_path = format!("{}/{hash}", *BUN_BUNDLE_CACHE_DIR);
|
||||
|
||||
#[cfg(windows)]
|
||||
let local_path = local_path.replace("/tmp", r"C:\tmp").replace("/", r"\");
|
||||
@@ -1495,7 +1495,7 @@ try {{
|
||||
},
|
||||
),
|
||||
)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ use tokio::process::Command;
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
|
||||
use windmill_common::flows::Step;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_types::s3::{LargeFileStorage, ObjectStoreResource, S3Object};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_object_store::get_etag_or_empty;
|
||||
use windmill_common::variables::{build_crypt_with_key_suffix, decrypt};
|
||||
use windmill_common::worker::{
|
||||
to_raw_value, update_ping_for_failed_init_script_query, write_file, Connection, Ping, PingType,
|
||||
@@ -32,6 +28,10 @@ use windmill_common::{
|
||||
utils::configure_client,
|
||||
variables::ContextualVariable,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_object_store::get_etag_or_empty;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_types::s3::{LargeFileStorage, ObjectStoreResource, S3Object};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use windmill_parser_sql::{s3_mode_extension, S3ModeArgs, S3ModeFormat};
|
||||
@@ -1090,7 +1090,7 @@ fn tentatively_improve_error(err: Error, executable: &str) -> Error {
|
||||
|
||||
pub async fn clean_cache() -> error::Result<()> {
|
||||
tracing::info!("Started cleaning cache");
|
||||
tokio::fs::remove_dir_all(ROOT_CACHE_DIR).await?;
|
||||
tokio::fs::remove_dir_all(&*ROOT_CACHE_DIR).await?;
|
||||
tracing::info!("Finished cleaning cache");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1557,4 +1557,3 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,11 @@ use itertools::Itertools;
|
||||
#[cfg(feature = "csharp")]
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
#[cfg(feature = "csharp")]
|
||||
use windmill_common::{
|
||||
utils::calculate_hash,
|
||||
worker::write_file,
|
||||
};
|
||||
use windmill_common::{utils::calculate_hash, worker::write_file};
|
||||
|
||||
use windmill_common::error::{self, Error};
|
||||
#[cfg(feature = "csharp")]
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_common::error::{self, Error};
|
||||
#[cfg(feature = "csharp")]
|
||||
use windmill_queue::append_logs;
|
||||
|
||||
@@ -105,8 +102,8 @@ pub async fn generate_nuget_lockfile(
|
||||
let mut gen_lockfile_cmd = Command::new(DOTNET_PATH.as_str());
|
||||
gen_lockfile_cmd
|
||||
.current_dir(job_dir)
|
||||
.env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget"))
|
||||
.env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR))
|
||||
.env("DOTNET_CLI_TELEMETRY_OPTOUT", "true")
|
||||
.env("DOTNET_NOLOGO", "true")
|
||||
.env("MSBUILDDISABLENODEREUSE", "1")
|
||||
@@ -367,8 +364,8 @@ async fn build_cs_proj(
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget"))
|
||||
.env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR))
|
||||
.env("DOTNET_CLI_TELEMETRY_OPTOUT", "true")
|
||||
.env("DOTNET_NOLOGO", "true")
|
||||
.env("MSBUILDDISABLENODEREUSE", "1")
|
||||
@@ -434,7 +431,7 @@ async fn build_cs_proj(
|
||||
}
|
||||
}
|
||||
|
||||
let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *CSHARP_CACHE_DIR);
|
||||
#[cfg(unix)]
|
||||
let target = format!("{job_dir}/Main");
|
||||
#[cfg(windows)]
|
||||
@@ -516,11 +513,10 @@ pub async fn handle_csharp_job(
|
||||
inner_content,
|
||||
requirements_o.unwrap_or(&String::new())
|
||||
));
|
||||
let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *CSHARP_CACHE_DIR);
|
||||
let remote_path = format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}");
|
||||
|
||||
let (cache, cache_logs) =
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
#[cfg(unix)]
|
||||
@@ -591,11 +587,11 @@ pub async fn handle_csharp_job(
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_CSHARP_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", CSHARP_CACHE_DIR)
|
||||
.replace("{CACHE_DIR}", &*CSHARP_CACHE_DIR)
|
||||
.replace("{CACHE_HASH}", &hash)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
@@ -608,8 +604,8 @@ pub async fn handle_csharp_job(
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget"))
|
||||
.env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR))
|
||||
.env("DOTNET_CLI_TELEMETRY_OPTOUT", "true")
|
||||
.env("DOTNET_NOLOGO", "true")
|
||||
.env("DOTNET_ROOT", DOTNET_ROOT.as_str())
|
||||
@@ -640,8 +636,8 @@ pub async fn handle_csharp_job(
|
||||
.envs(get_proxy_envs_for_lang(&ScriptLang::CSharp).await?)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget"))
|
||||
.env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR)
|
||||
.env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR))
|
||||
.env("DOTNET_CLI_TELEMETRY_OPTOUT", "true")
|
||||
.env("DOTNET_NOLOGO", "true")
|
||||
.env("DOTNET_ROOT", DOTNET_ROOT.as_str())
|
||||
|
||||
@@ -443,7 +443,8 @@ try {{
|
||||
}
|
||||
|
||||
let allow_read = format!(
|
||||
"--allow-read=./,/tmp/windmill/cache/deno/,{}",
|
||||
"--allow-read=./,{}/,{}",
|
||||
*DENO_CACHE_DIR,
|
||||
DENO_PATH.as_str()
|
||||
);
|
||||
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
|
||||
@@ -504,7 +505,8 @@ try {{
|
||||
*has_stream = handle_result.result_stream.is_some();
|
||||
|
||||
// logs.push_str(format!("execute: {:?}\n", start.elapsed().as_millis()).as_str());
|
||||
if let Err(e) = tokio::fs::remove_dir_all(format!("{DENO_CACHE_DIR}/gen/file/{job_dir}")).await
|
||||
if let Err(e) =
|
||||
tokio::fs::remove_dir_all(format!("{}/gen/file/{job_dir}", *DENO_CACHE_DIR)).await
|
||||
{
|
||||
tracing::error!("failed to remove deno gen tmp cache dir: {}", e);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,22 @@ pub async fn do_duckdb(
|
||||
let base_internal_url = client.base_internal_url.clone();
|
||||
let w_id = job.workspace_id.clone();
|
||||
|
||||
if annotations.prepare {
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
prepare_duckdb_ffi_safe(
|
||||
query_block_list.iter().map(String::as_str),
|
||||
&token,
|
||||
&base_internal_url,
|
||||
&w_id,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| Error::from(to_anyhow(e)))
|
||||
.and_then(|r| r)?;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
run_duckdb_ffi_safe(
|
||||
query_block_list.iter().map(String::as_str),
|
||||
@@ -248,6 +264,18 @@ struct DuckDbFfiLib {
|
||||
collect_first_row_only: bool,
|
||||
) -> *mut c_char,
|
||||
>,
|
||||
prepare_duckdb_ffi: Option<
|
||||
Symbol<
|
||||
'static,
|
||||
unsafe extern "C" fn(
|
||||
query_block_list: *const *const c_char,
|
||||
query_block_list_count: usize,
|
||||
token: *const c_char,
|
||||
base_internal_url: *const c_char,
|
||||
w_id: *const c_char,
|
||||
) -> *mut c_char,
|
||||
>,
|
||||
>,
|
||||
free_cstr: Symbol<'static, unsafe extern "C" fn(string: *mut c_char) -> ()>,
|
||||
}
|
||||
|
||||
@@ -307,8 +335,11 @@ impl DuckDbFfiLib {
|
||||
}
|
||||
}
|
||||
|
||||
let prepare_duckdb_ffi = unsafe { lib.get(b"prepare_duckdb_ffi").ok() };
|
||||
|
||||
Ok(DuckDbFfiLib {
|
||||
run_duckdb_ffi: unsafe { lib.get(b"run_duckdb_ffi").map_err(to_anyhow)? },
|
||||
prepare_duckdb_ffi,
|
||||
free_cstr: unsafe { lib.get(b"free_cstr").map_err(to_anyhow)? },
|
||||
})
|
||||
}
|
||||
@@ -388,6 +419,56 @@ fn run_duckdb_ffi_safe<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_duckdb_ffi_safe<'a>(
|
||||
query_block_list: impl Iterator<Item = &'a str>,
|
||||
token: &str,
|
||||
base_internal_url: &str,
|
||||
w_id: &str,
|
||||
) -> Result<Box<RawValue>> {
|
||||
let query_block_list = query_block_list
|
||||
.map(|s| {
|
||||
CString::new(s).map_err(|e| {
|
||||
Error::ExecutionErr(format!("Failed CString conversion: {}", e.to_string()))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let query_block_list = query_block_list
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let token = CString::new(token).map_err(to_anyhow)?;
|
||||
let base_internal_url = CString::new(base_internal_url).map_err(to_anyhow)?;
|
||||
let w_id = CString::new(w_id).map_err(to_anyhow)?;
|
||||
|
||||
let lib = DuckDbFfiLib::get_singleton()?;
|
||||
let prepare_fn = lib.prepare_duckdb_ffi.as_ref().ok_or_else(|| {
|
||||
Error::InternalErr(
|
||||
"prepare_duckdb_ffi not available in duckdb ffi library. Please update to the latest windmill_duckdb_ffi_lib.".to_string(),
|
||||
)
|
||||
})?;
|
||||
let free_cstr = &lib.free_cstr;
|
||||
|
||||
let result_str = unsafe {
|
||||
let ptr = prepare_fn(
|
||||
query_block_list.as_ptr(),
|
||||
query_block_list.len(),
|
||||
token.as_ptr(),
|
||||
base_internal_url.as_ptr(),
|
||||
w_id.as_ptr(),
|
||||
);
|
||||
let str = CStr::from_ptr(ptr).to_string_lossy().to_string();
|
||||
free_cstr(ptr);
|
||||
str
|
||||
};
|
||||
|
||||
if result_str.starts_with("ERROR") {
|
||||
Err(Error::ExecutionErr(result_str[6..].to_string()))
|
||||
} else {
|
||||
Ok(serde_json::value::RawValue::from_string(result_str).map_err(to_anyhow)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct ParsedAttachDbResource<'a> {
|
||||
resource_path: &'a str,
|
||||
name: &'a str,
|
||||
|
||||
@@ -18,8 +18,8 @@ pub async fn build_tar_and_push(
|
||||
custom_folder_name: Option<String>,
|
||||
platform_agnostic: bool,
|
||||
) -> error::Result<()> {
|
||||
use windmill_object_store::object_store_reexports::Path;
|
||||
use tokio::fs::create_dir_all;
|
||||
use windmill_object_store::object_store_reexports::Path;
|
||||
|
||||
use crate::TAR_PYBASE_CACHE_DIR;
|
||||
|
||||
@@ -33,7 +33,7 @@ pub async fn build_tar_and_push(
|
||||
folder.split("/").last().unwrap().to_owned()
|
||||
};
|
||||
|
||||
let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", lang);
|
||||
let prefix = &format!("{}/{}", *TAR_PYBASE_CACHE_DIR, lang);
|
||||
let tar_path = format!("{prefix}/{folder_name}_tar.tar");
|
||||
|
||||
create_dir_all(prefix).await?;
|
||||
@@ -197,7 +197,9 @@ pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
return os
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(_remote_path))
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(
|
||||
_remote_path,
|
||||
))
|
||||
.await
|
||||
.is_ok();
|
||||
}
|
||||
@@ -221,7 +223,7 @@ pub async fn save_cache(
|
||||
let file_to_cache = if is_dir {
|
||||
let tar_path = format!(
|
||||
"{}/tar/{}_tar.tar",
|
||||
windmill_common::worker::ROOT_CACHE_DIR,
|
||||
*windmill_common::worker::ROOT_CACHE_DIR,
|
||||
local_cache_path
|
||||
.split("/")
|
||||
.last()
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{common::MaybeLock, get_proxy_envs_for_lang};
|
||||
use std::{collections::HashMap, fs::DirBuilder, process::Stdio};
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
|
||||
use crate::global_cache::save_cache;
|
||||
use itertools::Itertools;
|
||||
use serde_json::value::RawValue;
|
||||
use tokio::{
|
||||
@@ -15,7 +16,6 @@ use windmill_common::{
|
||||
utils::calculate_hash,
|
||||
worker::{write_file, Connection, GoAnnotations},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE};
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
|
||||
|
||||
@@ -108,10 +108,9 @@ pub async fn handle_go_job(
|
||||
.expect("could not create go job dir");
|
||||
|
||||
let hash = calculate_hash(&format!("{}{:?}v2", inner_content, &maybe_lock));
|
||||
let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *GO_BIN_CACHE_DIR);
|
||||
let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}");
|
||||
let (cache, cache_logs) =
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let (skip_go_mod, skip_tidy) = if cache {
|
||||
(true, true)
|
||||
@@ -238,15 +237,15 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.env("GOPATH", {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
GO_CACHE_DIR
|
||||
GO_CACHE_DIR.as_str()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_gopath()
|
||||
&windows_gopath()
|
||||
}
|
||||
})
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.env("GOCACHE", GO_CACHE_DIR)
|
||||
.env("GOCACHE", GO_CACHE_DIR.as_str())
|
||||
.envs(PROXY_ENVS.clone())
|
||||
.args(vec!["build", "main.go"])
|
||||
.stdout(Stdio::piped())
|
||||
@@ -347,7 +346,7 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
@@ -384,11 +383,11 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.env("GOPATH", {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
GO_CACHE_DIR
|
||||
GO_CACHE_DIR.as_str()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_gopath()
|
||||
&windows_gopath()
|
||||
}
|
||||
})
|
||||
.env("HOME", HOME_ENV.as_str());
|
||||
@@ -508,7 +507,7 @@ pub async fn install_go_dependencies(
|
||||
#[cfg(windows)]
|
||||
child_cmd.env("GOPATH", windows_gopath());
|
||||
#[cfg(unix)]
|
||||
child_cmd.env("GOPATH", GO_CACHE_DIR);
|
||||
child_cmd.env("GOPATH", GO_CACHE_DIR.as_str());
|
||||
|
||||
#[cfg(windows)]
|
||||
set_windows_env_vars(&mut child_cmd);
|
||||
@@ -591,11 +590,11 @@ pub async fn install_go_dependencies(
|
||||
.env("GOPATH", {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
GO_CACHE_DIR
|
||||
GO_CACHE_DIR.as_str()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_gopath()
|
||||
&windows_gopath()
|
||||
}
|
||||
})
|
||||
.args(vec!["mod", mod_command])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, path::PathBuf, process::Stdio};
|
||||
|
||||
use crate::global_cache::save_cache;
|
||||
use anyhow::{anyhow, bail};
|
||||
use async_recursion::async_recursion;
|
||||
use itertools::Itertools;
|
||||
@@ -15,7 +16,6 @@ use windmill_common::{
|
||||
utils::calculate_hash,
|
||||
worker::{copy_dir_recursively, write_file, Connection},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_parser::Arg;
|
||||
use windmill_parser_java::parse_java_sig_meta;
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
|
||||
@@ -185,8 +185,8 @@ pub async fn resolve<'a>(
|
||||
cmd.env_clear()
|
||||
.current_dir(job_dir.to_owned())
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("HOME", JAVA_HOME_DIR)
|
||||
.env("COURSIER_CACHE", COURSIER_CACHE_DIR)
|
||||
.env("HOME", &*JAVA_HOME_DIR)
|
||||
.env("COURSIER_CACHE", &*COURSIER_CACHE_DIR)
|
||||
.envs(PROXY_ENVS.clone());
|
||||
|
||||
// Configure proxies
|
||||
@@ -208,7 +208,7 @@ pub async fn resolve<'a>(
|
||||
cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val));
|
||||
}
|
||||
}
|
||||
cmd.arg(&format!("-Duser.home={}", JAVA_HOME_DIR));
|
||||
cmd.arg(&format!("-Duser.home={}", *JAVA_HOME_DIR));
|
||||
if metadata(TRUST_STORE_PATH.clone()).await.is_ok() {
|
||||
cmd.args(&[
|
||||
&format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH),
|
||||
@@ -223,7 +223,7 @@ pub async fn resolve<'a>(
|
||||
"--parallel",
|
||||
&format!("{}", *JAVA_CONCURRENT_DOWNLOADS),
|
||||
"--cache",
|
||||
COURSIER_CACHE_DIR,
|
||||
&*COURSIER_CACHE_DIR,
|
||||
])
|
||||
.args(&get_repos(job_id, w_id, conn).await)
|
||||
.args(&deps.split("\n").collect_vec())
|
||||
@@ -276,7 +276,8 @@ async fn install<'a>(
|
||||
match (it.next(), it.next(), it.next()) {
|
||||
(Some(group_id), Some(artifact_id), Some(version)) => {
|
||||
let path = format!(
|
||||
"{JAVA_REPOSITORY_DIR}/{}/{artifact_id}/{version}",
|
||||
"{}/{}/{artifact_id}/{version}",
|
||||
*JAVA_REPOSITORY_DIR,
|
||||
group_id.replace(".", "/")
|
||||
);
|
||||
Ok(RequiredDependency {
|
||||
@@ -312,7 +313,7 @@ async fn install<'a>(
|
||||
metadata(TRUST_STORE_PATH.clone()).await,
|
||||
);
|
||||
let job_dir = job_dir.to_owned();
|
||||
let fetch_dir = format!("{JAVA_CACHE_DIR}/tmp-fetch-{}", Uuid::new_v4());
|
||||
let fetch_dir = format!("{}/tmp-fetch-{}", *JAVA_CACHE_DIR, Uuid::new_v4());
|
||||
let fetch_dir2 = fetch_dir.clone();
|
||||
par_install_language_dependencies_all_at_once(
|
||||
deps,
|
||||
@@ -334,8 +335,8 @@ async fn install<'a>(
|
||||
cmd.env_clear()
|
||||
.current_dir(&job_dir)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("HOME", JAVA_HOME_DIR)
|
||||
.env("COURSIER_CACHE", COURSIER_CACHE_DIR)
|
||||
.env("HOME", &*JAVA_HOME_DIR)
|
||||
.env("COURSIER_CACHE", &*COURSIER_CACHE_DIR)
|
||||
.envs(PROXY_ENVS.clone());
|
||||
// Configure proxies
|
||||
{
|
||||
@@ -357,7 +358,7 @@ async fn install<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
cmd.arg(&format!("-Duser.home={}", JAVA_HOME_DIR));
|
||||
cmd.arg(&format!("-Duser.home={}", *JAVA_HOME_DIR));
|
||||
if trust_store_metadata.is_ok() {
|
||||
cmd.args(&[
|
||||
&format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH),
|
||||
@@ -400,7 +401,7 @@ async fn install<'a>(
|
||||
if depth == 3 {
|
||||
copy_dir_recursively(
|
||||
&PathBuf::from(path),
|
||||
&PathBuf::from(JAVA_REPOSITORY_DIR),
|
||||
&PathBuf::from(&*JAVA_REPOSITORY_DIR),
|
||||
)?;
|
||||
|
||||
return Ok(());
|
||||
@@ -465,7 +466,7 @@ async fn compile<'a>(
|
||||
let reserved_variables =
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?;
|
||||
let hash = compute_hash(inner_content, *requirements_o);
|
||||
let bin_path = format!("{}/{hash}", JAVA_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *JAVA_CACHE_DIR);
|
||||
let remote_path = format!("java_jar/{hash}");
|
||||
let (cache, ..) = crate::global_cache::load_cache(&bin_path, &remote_path, true).await;
|
||||
|
||||
@@ -501,7 +502,7 @@ async fn compile<'a>(
|
||||
cmd.env_clear()
|
||||
.current_dir(job_dir.to_owned())
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("HOME", JAVA_HOME_DIR)
|
||||
.env("HOME", &*JAVA_HOME_DIR)
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
@@ -604,7 +605,7 @@ async fn run<'a>(
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_JAVA_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", JAVA_CACHE_DIR)
|
||||
.replace("{CACHE_DIR}", &*JAVA_CACHE_DIR)
|
||||
.replace("{SHARED_MOUNT}", &shared_mount)
|
||||
// .replace("{CACHED_TARGET}", &shared_mount)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()),
|
||||
@@ -613,7 +614,7 @@ async fn run<'a>(
|
||||
cmd.env_clear()
|
||||
.current_dir(job_dir)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("HOME", JAVA_HOME_DIR)
|
||||
.env("HOME", &*JAVA_HOME_DIR)
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
@@ -671,7 +672,7 @@ async fn run<'a>(
|
||||
cmd.env_clear()
|
||||
.current_dir(job_dir.to_owned())
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("HOME", JAVA_HOME_DIR)
|
||||
.env("HOME", &*JAVA_HOME_DIR)
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.envs(envs)
|
||||
.envs(reserved_variables);
|
||||
|
||||
@@ -16,8 +16,8 @@ use crate::{
|
||||
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
|
||||
read_result, start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang, handle_child, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV,
|
||||
TRACING_PROXY_CA_CERT_PATH,
|
||||
get_proxy_envs_for_lang, handle_child, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH,
|
||||
PATH_ENV, TRACING_PROXY_CA_CERT_PATH,
|
||||
};
|
||||
use windmill_common::client::AuthedClient;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
@@ -253,7 +253,7 @@ async fn run<'a>(
|
||||
.replace("{NU_PATH}", &NU_PATH)
|
||||
.replace("{SHARED_MOUNT}", &shared_mount)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
|
||||
@@ -159,7 +159,7 @@ try {
|
||||
|
||||
async fn scan_module_directories() -> Result<HashMap<String, String>, Error> {
|
||||
let mut module_dirs = HashMap::new();
|
||||
let cache_dir = std::path::Path::new(POWERSHELL_CACHE_DIR);
|
||||
let cache_dir = std::path::Path::new(&*POWERSHELL_CACHE_DIR);
|
||||
|
||||
if let Ok(entries) = fs::read_dir(cache_dir) {
|
||||
for entry in entries {
|
||||
@@ -391,7 +391,7 @@ pub async fn handle_powershell_job(
|
||||
.join(", ");
|
||||
|
||||
let install_string = generate_powershell_install_code()
|
||||
.replace("{path}", POWERSHELL_CACHE_DIR)
|
||||
.replace("{path}", &*POWERSHELL_CACHE_DIR)
|
||||
.replace("{job_id}", &job.id.to_string())
|
||||
.replace("{has_private_repo}", &format!("${has_private_repo}"))
|
||||
.replace("{has_credentials}", &format!("${has_credentials}"))
|
||||
@@ -442,7 +442,7 @@ $PSModulePathBackup = $env:PSModulePath
|
||||
$env:PSModulePath = \"$PSHome/Modules\"
|
||||
Get-Module -ListAvailable | Import-Module
|
||||
$env:PSModulePath = \"{}:$PSModulePathBackup\"",
|
||||
POWERSHELL_CACHE_DIR
|
||||
*POWERSHELL_CACHE_DIR
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -452,7 +452,7 @@ $PSModulePathBackup = $env:PSModulePath
|
||||
$env:PSModulePath = \"C:\\Program Files\\PowerShell\\7\\Modules\"
|
||||
Get-Module -ListAvailable | Import-Module
|
||||
$env:PSModulePath = \"{};$PSModulePathBackup\"",
|
||||
POWERSHELL_CACHE_DIR
|
||||
*POWERSHELL_CACHE_DIR
|
||||
);
|
||||
|
||||
// NOTE: powershell error handling / termination is quite tricky compared to bash
|
||||
@@ -525,7 +525,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{CACHE_DIR}", POWERSHELL_CACHE_DIR),
|
||||
.replace("{CACHE_DIR}", &*POWERSHELL_CACHE_DIR),
|
||||
)?;
|
||||
let cmd_args = vec![
|
||||
"--config",
|
||||
|
||||
@@ -278,7 +278,7 @@ pub async fn uv_pip_compile(
|
||||
"requirements.txt",
|
||||
// Target to /tmp/windmill/cache/uv
|
||||
"--cache-dir",
|
||||
UV_CACHE_DIR,
|
||||
&*UV_CACHE_DIR,
|
||||
];
|
||||
|
||||
args.extend(["-p", &py_version_str, "--python-preference", "only-managed"]);
|
||||
@@ -805,7 +805,7 @@ mount {{
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_PYTHON3_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{PY_INSTALL_DIR}", PY_INSTALL_DIR)
|
||||
.replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{SHARED_DEPENDENCIES}", shared_deps.as_str())
|
||||
@@ -815,7 +815,7 @@ mount {{
|
||||
"{ADDITIONAL_PYTHON_PATHS}",
|
||||
additional_python_paths_folders.as_str(),
|
||||
)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL),
|
||||
)?;
|
||||
} else {
|
||||
@@ -1410,10 +1410,10 @@ async fn spawn_uv_install(
|
||||
&nsjail_proto,
|
||||
NSJAIL_CONFIG_DOWNLOAD_PY_CONTENT
|
||||
.replace("{WORKER_DIR}", worker_dir)
|
||||
.replace("{PY_INSTALL_DIR}", &PY_INSTALL_DIR)
|
||||
.replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR)
|
||||
.replace("{TARGET_DIR}", &venv_p)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL)
|
||||
.as_str(),
|
||||
)?;
|
||||
|
||||
@@ -18,6 +18,8 @@ use windmill_common::{
|
||||
use anyhow::{anyhow, bail};
|
||||
use windmill_queue::append_logs;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::python_executor::UV_PATH;
|
||||
use crate::{
|
||||
common::{start_child_process, OccupancyMetrics},
|
||||
handle_child::handle_child,
|
||||
@@ -25,8 +27,6 @@ use crate::{
|
||||
HOME_ENV, INSTANCE_PYTHON_VERSION, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, UV_CACHE_DIR,
|
||||
WIN_ENVS,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use crate::python_executor::UV_PATH;
|
||||
|
||||
impl From<PyV> for PyVAlias {
|
||||
fn from(value: PyV) -> Self {
|
||||
@@ -234,7 +234,8 @@ impl PyV {
|
||||
pub(crate) fn to_cache_dir(&self, ignore_patch: bool) -> String {
|
||||
use windmill_common::worker::ROOT_CACHE_DIR;
|
||||
format!(
|
||||
"{ROOT_CACHE_DIR}{}",
|
||||
"{}{}",
|
||||
*ROOT_CACHE_DIR,
|
||||
self.to_cache_dir_top_level(ignore_patch)
|
||||
)
|
||||
}
|
||||
@@ -311,7 +312,7 @@ impl PyV {
|
||||
Command::new(uv_cmd)
|
||||
.env_clear()
|
||||
.envs(WIN_ENVS.to_vec())
|
||||
.env("UV_CACHE_DIR", UV_CACHE_DIR)
|
||||
.env("UV_CACHE_DIR", &*UV_CACHE_DIR)
|
||||
.args([
|
||||
"python",
|
||||
"list",
|
||||
@@ -539,8 +540,8 @@ impl PyV {
|
||||
])
|
||||
// TODO: Do we need these?
|
||||
.envs([
|
||||
("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR),
|
||||
("UV_CACHE_DIR", UV_CACHE_DIR),
|
||||
("UV_PYTHON_INSTALL_DIR", &*PY_INSTALL_DIR),
|
||||
("UV_CACHE_DIR", &*UV_CACHE_DIR),
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
@@ -630,11 +631,9 @@ impl PyV {
|
||||
"--system",
|
||||
"--python-preference=only-managed",
|
||||
])
|
||||
.envs([
|
||||
("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR),
|
||||
("UV_PYTHON_PREFERENCE", "only-managed"),
|
||||
("UV_CACHE_DIR", UV_CACHE_DIR),
|
||||
])
|
||||
.env("UV_PYTHON_INSTALL_DIR", &*PY_INSTALL_DIR)
|
||||
.env("UV_PYTHON_PREFERENCE", "only-managed")
|
||||
.env("UV_CACHE_DIR", &*UV_CACHE_DIR)
|
||||
// .stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::{collections::HashMap, process::Stdio};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use const_format::concatcp;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use tokio::{
|
||||
@@ -122,7 +121,7 @@ pub async fn prepare<'a>(
|
||||
.write_all(&wrap(inner_content)?.into_bytes())
|
||||
.await?;
|
||||
|
||||
let mini_wm_path = format!("{RUBY_CACHE_DIR}/gems/windmill-internal/windmill");
|
||||
let mini_wm_path = format!("{}/gems/windmill-internal/windmill", *RUBY_CACHE_DIR);
|
||||
if !std::fs::metadata(&mini_wm_path).is_ok() {
|
||||
fs::create_dir_all(&mini_wm_path).await?;
|
||||
|
||||
@@ -339,7 +338,7 @@ Your Gemfile syntax will continue to work as-is."
|
||||
&NSJAIL_CONFIG_LOCK_RUBY_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir),
|
||||
)?;
|
||||
let mut cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
@@ -588,7 +587,7 @@ async fn install<'a>(
|
||||
// 123...zx-activesupport-8.0.2
|
||||
// ^^^^^^^^ hash based on source and type (GEM or GIT)
|
||||
let handle = format!("{}-{}-{}", hash, pkg, version);
|
||||
let path = format!("{RUBY_CACHE_DIR}/gems/{}", &handle);
|
||||
let path = format!("{}/gems/{}", *RUBY_CACHE_DIR, &handle);
|
||||
|
||||
deps.push(RequiredDependency {
|
||||
path,
|
||||
@@ -632,7 +631,7 @@ async fn install<'a>(
|
||||
&NSJAIL_CONFIG_DOWNLOAD_RUBY_CONTENT
|
||||
.replace("{TARGET}", &dependency.path)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir),
|
||||
)?;
|
||||
let mut cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
@@ -741,9 +740,9 @@ async fn install<'a>(
|
||||
};
|
||||
// Include builtin windmill client
|
||||
{
|
||||
const WM_INTERNAL: &str = concatcp!(RUBY_CACHE_DIR, "/gems/windmill-internal");
|
||||
res.top_level_paths.push(WM_INTERNAL.to_owned());
|
||||
res.rubylib += format!(":{WM_INTERNAL}").as_str();
|
||||
let wm_internal = format!("{}/gems/windmill-internal", *RUBY_CACHE_DIR);
|
||||
res.top_level_paths.push(wm_internal.clone());
|
||||
res.rubylib += format!(":{wm_internal}").as_str();
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
@@ -800,7 +799,7 @@ mount {{
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{SHARED_MOUNT}", &shared_mount)
|
||||
.replace("{SHARED_DEPENDENCIES}", &shared_deps)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()),
|
||||
)?;
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::{collections::HashMap, process::Stdio};
|
||||
use uuid::Uuid;
|
||||
use windmill_parser_rust::parse_rust_deps_into_manifest;
|
||||
|
||||
use crate::global_cache::save_cache;
|
||||
use itertools::Itertools;
|
||||
use tokio::{
|
||||
fs::{create_dir_all, File},
|
||||
@@ -16,7 +17,6 @@ use windmill_common::{
|
||||
utils::calculate_hash,
|
||||
worker::{write_file, Connection},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_queue::MiniPulledJob;
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
|
||||
@@ -337,7 +337,7 @@ async fn get_build_dir(
|
||||
if !is_sandboxing_enabled() {
|
||||
// If nsjail is disabled then entire worker has shared build directory
|
||||
// It drastically improves cache hit-rate.
|
||||
Some((format!("{RUST_CACHE_DIR}/build/{worker_name}"), true))
|
||||
Some((format!("{}/build/{worker_name}", *RUST_CACHE_DIR), true))
|
||||
} else {
|
||||
// If nsjail is enabled, having global shared directory is vulnerability and target for an attack
|
||||
// Instead we either:
|
||||
@@ -345,7 +345,8 @@ async fn get_build_dir(
|
||||
// 2. If user is not known or something else goes wrong - use random build dir. This is equivalent to no cache at all.
|
||||
Some((
|
||||
format!(
|
||||
"{RUST_CACHE_DIR}/build/{}@{}@{}",
|
||||
"{}/build/{}@{}@{}",
|
||||
*RUST_CACHE_DIR,
|
||||
&job.workspace_id,
|
||||
p.replace('/', "."),
|
||||
&job.created_by
|
||||
@@ -355,7 +356,10 @@ async fn get_build_dir(
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or((format!("{RUST_CACHE_DIR}/build/{}", Uuid::new_v4()), false));
|
||||
.unwrap_or((
|
||||
format!("{}/build/{}", *RUST_CACHE_DIR, Uuid::new_v4()),
|
||||
false,
|
||||
));
|
||||
|
||||
{
|
||||
let (t, r, g) = (
|
||||
@@ -449,7 +453,7 @@ pub async fn build_rust_crate(
|
||||
is_preview: bool,
|
||||
) -> error::Result<String> {
|
||||
ensure_rust_runtime_dirs();
|
||||
let bin_path = format!("{}/{hash}", RUST_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *RUST_CACHE_DIR);
|
||||
|
||||
let build_dir = get_build_dir(job, job_dir, conn, worker_name, is_preview).await?;
|
||||
|
||||
@@ -459,9 +463,9 @@ pub async fn build_rust_crate(
|
||||
"download.config.proto",
|
||||
&NSJAIL_CONFIG_COMPILE_RUST_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", RUST_CACHE_DIR)
|
||||
.replace("{CACHE_DIR}", &*RUST_CACHE_DIR)
|
||||
.replace("{CARGO_HOME}", CARGO_HOME.as_str())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL)
|
||||
.replace("{BUILD}", &build_dir),
|
||||
)?;
|
||||
@@ -605,14 +609,13 @@ pub async fn handle_rust_job(
|
||||
check_executor_binary_exists("cargo", CARGO_PATH.as_str(), "rust")?;
|
||||
|
||||
let hash = compute_rust_hash(inner_content, requirements_o);
|
||||
let bin_path = format!("{}/{hash}", RUST_CACHE_DIR);
|
||||
let bin_path = format!("{}/{hash}", *RUST_CACHE_DIR);
|
||||
let remote_path = format!("{RUST_OBJECT_STORE_PREFIX}{hash}");
|
||||
|
||||
let reserved_variables =
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
|
||||
|
||||
let (cache, cache_logs) =
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
let target = format!("{job_dir}/main");
|
||||
@@ -669,10 +672,10 @@ pub async fn handle_rust_job(
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_RUST_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", RUST_CACHE_DIR)
|
||||
.replace("{CACHE_DIR}", &*RUST_CACHE_DIR)
|
||||
.replace("{CACHE_HASH}", &hash)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH)
|
||||
.replace("#{DEV}", DEV_CONF_NSJAIL)
|
||||
.replace("{SHARED_MOUNT}", shared_mount),
|
||||
)?;
|
||||
|
||||
@@ -37,7 +37,7 @@ use windmill_common::{
|
||||
utils::{create_directory_async, WarnAfterExt},
|
||||
worker::{
|
||||
make_pull_query, write_file, Connection, HttpClient, MAX_TIMEOUT,
|
||||
MIN_PERIODIC_SCRIPT_INTERVAL_SECONDS, ROOT_CACHE_DIR, ROOT_CACHE_NOMOUNT_DIR, TMP_DIR,
|
||||
MIN_PERIODIC_SCRIPT_INTERVAL_SECONDS, ROOT_CACHE_DIR, ROOT_CACHE_NOMOUNT_DIR, WINDMILL_DIR,
|
||||
},
|
||||
worker_group_job_stats::JobStatsMap,
|
||||
KillpillSender,
|
||||
@@ -47,7 +47,6 @@ use windmill_common::{
|
||||
use windmill_common::ee_oss::LICENSE_KEY_VALID;
|
||||
|
||||
use anyhow::Result;
|
||||
use const_format::concatcp;
|
||||
#[cfg(feature = "prometheus")]
|
||||
use prometheus::IntCounter;
|
||||
|
||||
@@ -196,45 +195,47 @@ use windmill_common::bench::{benchmark_init, benchmark_verify, BenchmarkInfo, Be
|
||||
|
||||
use windmill_common::add_time;
|
||||
|
||||
pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_10");
|
||||
pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_11");
|
||||
pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_12");
|
||||
pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_13");
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PY310_CACHE_DIR: String = format!("{}python_3_10", *ROOT_CACHE_DIR);
|
||||
pub static ref PY311_CACHE_DIR: String = format!("{}python_3_11", *ROOT_CACHE_DIR);
|
||||
pub static ref PY312_CACHE_DIR: String = format!("{}python_3_12", *ROOT_CACHE_DIR);
|
||||
pub static ref PY313_CACHE_DIR: String = format!("{}python_3_13", *ROOT_CACHE_DIR);
|
||||
|
||||
pub const TAR_JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/java");
|
||||
pub static ref TAR_JAVA_CACHE_DIR: String = format!("{}tar/java", *ROOT_CACHE_DIR);
|
||||
|
||||
pub const UV_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "uv");
|
||||
pub const PY_INSTALL_DIR: &str = concatcp!(ROOT_CACHE_DIR, "py_runtime");
|
||||
pub const TAR_PYBASE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar");
|
||||
pub const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno");
|
||||
pub const DENO_CACHE_DIR_DEPS: &str = concatcp!(ROOT_CACHE_DIR, "deno/deps");
|
||||
pub const DENO_CACHE_DIR_NPM: &str = concatcp!(ROOT_CACHE_DIR, "deno/npm");
|
||||
pub static ref UV_CACHE_DIR: String = format!("{}uv", *ROOT_CACHE_DIR);
|
||||
pub static ref PY_INSTALL_DIR: String = format!("{}py_runtime", *ROOT_CACHE_DIR);
|
||||
pub static ref TAR_PYBASE_CACHE_DIR: String = format!("{}tar", *ROOT_CACHE_DIR);
|
||||
pub static ref DENO_CACHE_DIR: String = format!("{}deno", *ROOT_CACHE_DIR);
|
||||
pub static ref DENO_CACHE_DIR_DEPS: String = format!("{}deno/deps", *ROOT_CACHE_DIR);
|
||||
pub static ref DENO_CACHE_DIR_NPM: String = format!("{}deno/npm", *ROOT_CACHE_DIR);
|
||||
|
||||
pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
|
||||
pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust");
|
||||
pub const NU_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "nu");
|
||||
pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp");
|
||||
pub static ref GO_CACHE_DIR: String = format!("{}go", *ROOT_CACHE_DIR);
|
||||
pub static ref RUST_CACHE_DIR: String = format!("{}rust", *ROOT_CACHE_DIR);
|
||||
pub static ref NU_CACHE_DIR: String = format!("{}nu", *ROOT_CACHE_DIR);
|
||||
pub static ref CSHARP_CACHE_DIR: String = format!("{}csharp", *ROOT_CACHE_DIR);
|
||||
|
||||
// Java
|
||||
pub const JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "java");
|
||||
pub const COURSIER_CACHE_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/coursier-cache");
|
||||
pub const JAVA_REPOSITORY_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/repository");
|
||||
pub const JAVA_HOME_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/home");
|
||||
// Java
|
||||
pub static ref JAVA_CACHE_DIR: String = format!("{}java", *ROOT_CACHE_DIR);
|
||||
pub static ref COURSIER_CACHE_DIR: String = format!("{}/coursier-cache", *JAVA_CACHE_DIR);
|
||||
pub static ref JAVA_REPOSITORY_DIR: String = format!("{}/repository", *JAVA_CACHE_DIR);
|
||||
pub static ref JAVA_HOME_DIR: String = format!("{}/home", *JAVA_CACHE_DIR);
|
||||
|
||||
// Ruby
|
||||
pub const RUBY_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "ruby");
|
||||
// Ruby
|
||||
pub static ref RUBY_CACHE_DIR: String = format!("{}ruby", *ROOT_CACHE_DIR);
|
||||
|
||||
// for related places search: ADD_NEW_LANG
|
||||
pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun");
|
||||
pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun");
|
||||
pub const BUN_CODEBASE_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "script_bundle");
|
||||
// for related places search: ADD_NEW_LANG
|
||||
pub static ref BUN_CACHE_DIR: String = format!("{}bun", *ROOT_CACHE_NOMOUNT_DIR);
|
||||
pub static ref BUN_BUNDLE_CACHE_DIR: String = format!("{}bun", *ROOT_CACHE_DIR);
|
||||
pub static ref BUN_CODEBASE_BUNDLE_CACHE_DIR: String = format!("{}script_bundle", *ROOT_CACHE_NOMOUNT_DIR);
|
||||
|
||||
pub const GO_BIN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "gobin");
|
||||
pub const POWERSHELL_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "powershell");
|
||||
pub const COMPOSER_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "composer");
|
||||
pub static ref GO_BIN_CACHE_DIR: String = format!("{}gobin", *ROOT_CACHE_DIR);
|
||||
pub static ref POWERSHELL_CACHE_DIR: String = format!("{}powershell", *ROOT_CACHE_DIR);
|
||||
pub static ref COMPOSER_CACHE_DIR: String = format!("{}composer", *ROOT_CACHE_DIR);
|
||||
|
||||
pub const TRACING_PROXY_CA_CERT_PATH: &str =
|
||||
concatcp!(ROOT_CACHE_NOMOUNT_DIR, "tracing_proxy_ca.pem");
|
||||
pub static ref TRACING_PROXY_CA_CERT_PATH: String =
|
||||
format!("{}tracing_proxy_ca.pem", *ROOT_CACHE_NOMOUNT_DIR);
|
||||
}
|
||||
|
||||
const NUM_SECS_PING: u64 = 5;
|
||||
const NUM_SECS_READINGS: u64 = 60;
|
||||
@@ -1375,7 +1376,7 @@ pub async fn run_worker(
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let worker_dir = format!("{TMP_DIR}/{worker_name}");
|
||||
let worker_dir = format!("{}/{worker_name}", *WINDMILL_DIR);
|
||||
tracing::debug!(worker = %worker_name, hostname = %hostname, worker_dir = %worker_dir, "Creating worker dir");
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
|
||||
@@ -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.648.0";
|
||||
export const VERSION = "v1.649.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -67,7 +67,7 @@ export {
|
||||
workspaceAdd,
|
||||
};
|
||||
|
||||
export const VERSION = "1.648.0";
|
||||
export const VERSION = "1.649.0";
|
||||
|
||||
// Re-exported from constants.ts to maintain backwards compatibility
|
||||
export { WM_FORK_PREFIX } from "./core/constants.ts";
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
Total messages: 23 (Errors: 0, Warnings: 1)
|
||||
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] layoutNodes same nodes @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:516
|
||||
[DEBUG] [vite] connecting... @ http://localhost:5183/@vite/client:730
|
||||
[DEBUG] [vite] connected. @ http://localhost:5183/@vite/client:832
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] layoutNodes 3 @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:521
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:535
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:536
|
||||
[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024
|
||||
[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772544066222:100
|
||||
[LOG] ELK result: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 277,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 279,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 281,
|
||||
"x": 12,
|
||||
"y": 204
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Trigger-Input_s0",
|
||||
"startPoint": {
|
||||
"x": 150,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 150,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "Trigger",
|
||||
"outgoingShape": "Input"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Input-Result_s0",
|
||||
"startPoint": {
|
||||
"x": 150,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 150,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "Input",
|
||||
"outgoingShape": "Result"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
}
|
||||
],
|
||||
"$H": 13,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 299,
|
||||
"height": 250
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:540
|
||||
[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68
|
||||
[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] layoutNodes 14 @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:521
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:535
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.separateConnectedComponents": "false",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
|
||||
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
||||
"elk.layered.nodePlacement.bk.fixedAlignment": "LEFTUP"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:536
|
||||
[LOG] ELK result: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 352,
|
||||
"x": 385,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 354,
|
||||
"x": 385,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 356,
|
||||
"x": 385,
|
||||
"y": 636
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 358,
|
||||
"x": 385,
|
||||
"y": 540
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.separateConnectedComponents": "false",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
|
||||
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
||||
"elk.layered.nodePlacement.bk.fixedAlignment": "LEFTUP"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 364,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 366,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-b-c_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "b",
|
||||
"outgoingShape": "c"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-0"
|
||||
}
|
||||
],
|
||||
"$H": 362,
|
||||
"x": 12,
|
||||
"y": 60,
|
||||
"width": 299,
|
||||
"height": 154
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 370,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
}
|
||||
],
|
||||
"edges": [],
|
||||
"$H": 368,
|
||||
"x": 361,
|
||||
"y": 108,
|
||||
"width": 299,
|
||||
"height": 58
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 374,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 376,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 378,
|
||||
"x": 12,
|
||||
"y": 204
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-e-f_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "e",
|
||||
"outgoingShape": "f"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-2"
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-f-g_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "f",
|
||||
"outgoingShape": "g"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-2"
|
||||
}
|
||||
],
|
||||
"$H": 372,
|
||||
"x": 710,
|
||||
"y": 12,
|
||||
"width": 299,
|
||||
"height": 250
|
||||
}
|
||||
],
|
||||
"$H": 360,
|
||||
"x": 12,
|
||||
"y": 204,
|
||||
"width": 1021,
|
||||
"height": 274
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Trigger-Input_s0",
|
||||
"startPoint": {
|
||||
"x": 523,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 523,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "Trigger",
|
||||
"outgoingShape": "Input"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-a-end-Result_s0",
|
||||
"startPoint": {
|
||||
"x": 523,
|
||||
"y": 574
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 523,
|
||||
"y": 636
|
||||
},
|
||||
"incomingShape": "a-end",
|
||||
"outgoingShape": "Result"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Input-a_s0",
|
||||
"startPoint": {
|
||||
"x": 523,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 523,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "Input",
|
||||
"outgoingShape": "a"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-a-a-end_s0",
|
||||
"startPoint": {
|
||||
"x": 523,
|
||||
"y": 478
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 523,
|
||||
"y": 540
|
||||
},
|
||||
"incomingShape": "a",
|
||||
"outgoingShape": "a-end"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
}
|
||||
],
|
||||
"$H": 350,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1045,
|
||||
"height": 682
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:540
|
||||
[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65
|
||||
[DEBUG] layoutNodes same nodes @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:516
|
||||
@@ -0,0 +1,425 @@
|
||||
Total messages: 36 (Errors: 0, Warnings: 1)
|
||||
Returning 22 messages for level "info"
|
||||
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532
|
||||
[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024
|
||||
[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543608757:100
|
||||
[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68
|
||||
[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532
|
||||
[LOG] RawScript module b without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module b @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] RawScript module c without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module c @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] RawScript module d without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module d @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] RawScript module e without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module e @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] RawScript module f without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module f @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] RawScript module g without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149
|
||||
[LOG] Parsing assets for RawScript module g @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532
|
||||
@@ -0,0 +1,149 @@
|
||||
Total messages: 15 (Errors: 0, Warnings: 0)
|
||||
Returning 6 messages for level "info"
|
||||
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532
|
||||
[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024
|
||||
[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543608757:100
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchOneEnd) ← [a-branch-default], a-branch-default(branchOneStart) ← [a]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "RIGHT",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-default",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532
|
||||
@@ -0,0 +1,674 @@
|
||||
Total messages: 21 (Errors: 0, Warnings: 1)
|
||||
Returning 10 messages for level "info"
|
||||
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:532
|
||||
[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024
|
||||
[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543797839:100
|
||||
[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68
|
||||
[LOG] ELK result: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 277,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 279,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 281,
|
||||
"x": 12,
|
||||
"y": 204
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Trigger-Input_s0",
|
||||
"startPoint": {
|
||||
"x": 150,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 150,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "Trigger",
|
||||
"outgoingShape": "Input"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-Input-Result",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Input-Result_s0",
|
||||
"startPoint": {
|
||||
"x": 150,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 150,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "Input",
|
||||
"outgoingShape": "Result"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
}
|
||||
],
|
||||
"$H": 13,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 299,
|
||||
"height": 250
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:536
|
||||
[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211
|
||||
[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:531
|
||||
[LOG] ELK graph: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
]
|
||||
}
|
||||
]
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:532
|
||||
[LOG] ELK result: {
|
||||
"id": "root",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50",
|
||||
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
||||
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
||||
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "Trigger",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 352,
|
||||
"x": 36,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "Input",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 354,
|
||||
"x": 36,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "Result",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 356,
|
||||
"x": 36,
|
||||
"y": 888
|
||||
},
|
||||
{
|
||||
"id": "a-end",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 358,
|
||||
"x": 36,
|
||||
"y": 792
|
||||
},
|
||||
{
|
||||
"id": "a",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "a-branch-0",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "b",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 364,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 366,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-b-c",
|
||||
"sources": [
|
||||
"b"
|
||||
],
|
||||
"targets": [
|
||||
"c"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-b-c_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "b",
|
||||
"outgoingShape": "c"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-0"
|
||||
}
|
||||
],
|
||||
"$H": 362,
|
||||
"x": 12,
|
||||
"y": 90,
|
||||
"width": 299,
|
||||
"height": 154
|
||||
},
|
||||
{
|
||||
"id": "a-branch-1",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "d",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 370,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
}
|
||||
],
|
||||
"edges": [],
|
||||
"$H": 368,
|
||||
"x": 12,
|
||||
"y": 12,
|
||||
"width": 299,
|
||||
"height": 58
|
||||
},
|
||||
{
|
||||
"id": "a-branch-2",
|
||||
"layoutOptions": {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": "62",
|
||||
"elk.spacing.nodeNode": "50"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "e",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 374,
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
{
|
||||
"id": "f",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 376,
|
||||
"x": 12,
|
||||
"y": 108
|
||||
},
|
||||
{
|
||||
"id": "g",
|
||||
"width": 275,
|
||||
"height": 34,
|
||||
"$H": 378,
|
||||
"x": 12,
|
||||
"y": 204
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-e-f",
|
||||
"sources": [
|
||||
"e"
|
||||
],
|
||||
"targets": [
|
||||
"f"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-e-f_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "e",
|
||||
"outgoingShape": "f"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-2"
|
||||
},
|
||||
{
|
||||
"id": "e-f-g",
|
||||
"sources": [
|
||||
"f"
|
||||
],
|
||||
"targets": [
|
||||
"g"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-f-g_s0",
|
||||
"startPoint": {
|
||||
"x": 149.5,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 149.5,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "f",
|
||||
"outgoingShape": "g"
|
||||
}
|
||||
],
|
||||
"container": "a-branch-2"
|
||||
}
|
||||
],
|
||||
"$H": 372,
|
||||
"x": 12,
|
||||
"y": 264,
|
||||
"width": 299,
|
||||
"height": 250
|
||||
}
|
||||
],
|
||||
"$H": 360,
|
||||
"x": 12,
|
||||
"y": 204,
|
||||
"width": 323,
|
||||
"height": 526
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e-Trigger-Input",
|
||||
"sources": [
|
||||
"Trigger"
|
||||
],
|
||||
"targets": [
|
||||
"Input"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Trigger-Input_s0",
|
||||
"startPoint": {
|
||||
"x": 174,
|
||||
"y": 46
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 174,
|
||||
"y": 108
|
||||
},
|
||||
"incomingShape": "Trigger",
|
||||
"outgoingShape": "Input"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-a-end-Result",
|
||||
"sources": [
|
||||
"a-end"
|
||||
],
|
||||
"targets": [
|
||||
"Result"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-a-end-Result_s0",
|
||||
"startPoint": {
|
||||
"x": 174,
|
||||
"y": 826
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 174,
|
||||
"y": 888
|
||||
},
|
||||
"incomingShape": "a-end",
|
||||
"outgoingShape": "Result"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-Input-a",
|
||||
"sources": [
|
||||
"Input"
|
||||
],
|
||||
"targets": [
|
||||
"a"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-Input-a_s0",
|
||||
"startPoint": {
|
||||
"x": 174,
|
||||
"y": 142
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 174,
|
||||
"y": 204
|
||||
},
|
||||
"incomingShape": "Input",
|
||||
"outgoingShape": "a"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
},
|
||||
{
|
||||
"id": "e-a-a-end",
|
||||
"sources": [
|
||||
"a"
|
||||
],
|
||||
"targets": [
|
||||
"a-end"
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"id": "e-a-a-end_s0",
|
||||
"startPoint": {
|
||||
"x": 174,
|
||||
"y": 730
|
||||
},
|
||||
"endPoint": {
|
||||
"x": 174,
|
||||
"y": 792
|
||||
},
|
||||
"incomingShape": "a",
|
||||
"outgoingShape": "a-end"
|
||||
}
|
||||
],
|
||||
"container": "root"
|
||||
}
|
||||
],
|
||||
"$H": 350,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 347,
|
||||
"height": 934
|
||||
} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:536
|
||||
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 116 KiB |
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.648.0",
|
||||
"version": "1.649.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.648.0",
|
||||
"version": "1.649.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.648.0",
|
||||
"version": "1.649.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -6,27 +6,20 @@
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
import { ArrowLeft, Expand, LoaderCircle, Minimize, RefreshCcw } from 'lucide-svelte'
|
||||
import type { DbInput } from './dbTypes'
|
||||
import DBManagerContent from './DBManagerContent.svelte'
|
||||
import { resource } from 'runed'
|
||||
import { untrack } from 'svelte'
|
||||
import type { DbManagerUriState } from './dbManagerDrawerModel.svelte'
|
||||
|
||||
interface Props {
|
||||
uriState: DbManagerUriState
|
||||
/** Z-index offset for the drawer, useful when opening from within modals */
|
||||
offset?: number
|
||||
}
|
||||
|
||||
let { offset = 0 }: Props = $props()
|
||||
let { uriState, offset = 0 }: Props = $props()
|
||||
|
||||
let input: DbInput | undefined = $state()
|
||||
let open = $derived(!!input)
|
||||
|
||||
// For datatable inputs, track the selected datatable separately
|
||||
let selectedDatatable = $state<string | undefined>(undefined)
|
||||
|
||||
// Check if input is a datatable type
|
||||
const isDatatableInput = $derived(
|
||||
input?.type === 'database' && input.resourcePath.startsWith('datatable://')
|
||||
)
|
||||
let open = $derived(uriState.open)
|
||||
|
||||
// Load available datatables when drawer opens with datatable input
|
||||
const datatables = resource<string[]>([], async () => {
|
||||
@@ -39,16 +32,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Computed input that updates when selectedDatatable changes
|
||||
const effectiveInput: DbInput | undefined = $derived.by(() => {
|
||||
if (!input) return undefined
|
||||
if (!isDatatableInput || !selectedDatatable) return input
|
||||
return {
|
||||
...input,
|
||||
resourcePath: `datatable://${selectedDatatable}`
|
||||
}
|
||||
})
|
||||
|
||||
const datatableItems = $derived(
|
||||
datatables.current.map((dt) => ({
|
||||
value: dt,
|
||||
@@ -56,32 +39,26 @@
|
||||
}))
|
||||
)
|
||||
|
||||
export function openDrawer(nInput: DbInput) {
|
||||
input = nInput
|
||||
if (isDatatableInput) {
|
||||
datatables.refetch()
|
||||
// Refetch datatables when switching to a datatable input
|
||||
$effect(() => {
|
||||
if (uriState.isDatatableInput) {
|
||||
untrack(() => datatables.refetch())
|
||||
}
|
||||
// If it's a datatable input, extract the datatable name for the selector
|
||||
if (nInput.type === 'database' && nInput.resourcePath.startsWith('datatable://')) {
|
||||
selectedDatatable = nInput.resourcePath.replace('datatable://', '')
|
||||
datatables.refetch()
|
||||
} else {
|
||||
selectedDatatable = undefined
|
||||
}
|
||||
}
|
||||
export function closeDrawer() {
|
||||
input = undefined
|
||||
selectedDatatable = undefined
|
||||
})
|
||||
|
||||
function handleClose() {
|
||||
uriState.closeDrawer()
|
||||
dbManagerContent?.clearReplResult()
|
||||
if (window.location.hash.startsWith('#dbmanager:'))
|
||||
history.replaceState('', document.title, window.location.href.replace(/#dbmanager:.*$/, ''))
|
||||
}
|
||||
|
||||
let windowWidth = $state(window.innerWidth)
|
||||
let expand = $state(false)
|
||||
|
||||
$effect(() => {
|
||||
if (!open) expand = false
|
||||
if (!open) {
|
||||
expand = false
|
||||
uriState.closeDrawer()
|
||||
}
|
||||
})
|
||||
|
||||
let dbManagerContent: DBManagerContent | undefined = $state()
|
||||
@@ -96,7 +73,7 @@
|
||||
size={expand ? `${windowWidth}px` : '1200px'}
|
||||
preventEscape
|
||||
{offset}
|
||||
on:close={closeDrawer}
|
||||
on:close={handleClose}
|
||||
>
|
||||
<DrawerContent
|
||||
title={hasReplResult ? 'Query Result' : 'Database Manager'}
|
||||
@@ -104,18 +81,24 @@
|
||||
if (hasReplResult) {
|
||||
dbManagerContent?.clearReplResult()
|
||||
} else {
|
||||
closeDrawer()
|
||||
handleClose()
|
||||
}
|
||||
}}
|
||||
CloseIcon={hasReplResult ? ArrowLeft : undefined}
|
||||
noPadding
|
||||
id="db-manager-drawer"
|
||||
>
|
||||
{#if effectiveInput && $workspaceStore}
|
||||
{#key selectedDatatable}
|
||||
<DBManagerContent bind:this={dbManagerContent} input={effectiveInput} bind:hasReplResult>
|
||||
{#if uriState.effectiveInput && $workspaceStore}
|
||||
{#key uriState.selectedDatatable}
|
||||
<DBManagerContent
|
||||
bind:this={dbManagerContent}
|
||||
input={uriState.effectiveInput}
|
||||
bind:hasReplResult
|
||||
bind:selectedSchemaKey={uriState.selectedSchema}
|
||||
bind:selectedTableKey={uriState.selectedTable}
|
||||
>
|
||||
{#snippet dbSelector()}
|
||||
{#if isDatatableInput}
|
||||
{#if uriState.isDatatableInput}
|
||||
{#if datatables.loading}
|
||||
<div class="flex items-center gap-2 text-tertiary ml-2">
|
||||
<LoaderCircle size={14} class="animate-spin" />
|
||||
@@ -125,7 +108,7 @@
|
||||
<Select
|
||||
transformInputSelectedText={(s) => `Datatable: ${s}`}
|
||||
items={datatableItems}
|
||||
bind:value={selectedDatatable}
|
||||
bind:value={uriState.selectedDatatable}
|
||||
placeholder="Select data table"
|
||||
size="md"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
import Select from './select/Select.svelte'
|
||||
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
let datatables = usePromise(() =>
|
||||
WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' })
|
||||
)
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
@@ -49,7 +49,6 @@
|
||||
<ExploreAssetButton
|
||||
class="mt-1 w-fit"
|
||||
asset={{ kind: 'datatable', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
{item.displayName}
|
||||
</p>
|
||||
{@render item.extra?.()}
|
||||
{#if item.shortcut}
|
||||
<span class="ml-auto pl-4 text-2xs text-secondary shrink-0">{item.shortcut}</span>
|
||||
{/if}
|
||||
{#if item.tooltip}
|
||||
<Tooltip>
|
||||
{#snippet text()}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
import Select from './select/Select.svelte'
|
||||
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
let ducklakes = usePromise(() =>
|
||||
WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
|
||||
)
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
@@ -49,7 +49,6 @@
|
||||
<ExploreAssetButton
|
||||
class="mt-1 w-fit"
|
||||
asset={{ kind: 'ducklake', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
import { isDbType } from '$lib/components/dbTypes'
|
||||
import { formatAsset, type Asset } from '$lib/components/assets/lib'
|
||||
import { Button, ButtonType } from '$lib/components/common'
|
||||
import DbManagerDrawer from '$lib/components/DBManagerDrawer.svelte'
|
||||
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { globalDbManagerDrawer, userStore } from '$lib/stores'
|
||||
import { isS3Uri } from '$lib/utils'
|
||||
import { Database, File } from 'lucide-svelte'
|
||||
import DucklakeIcon from './icons/DucklakeIcon.svelte'
|
||||
@@ -27,7 +26,6 @@
|
||||
asset,
|
||||
_resourceMetadata,
|
||||
s3FilePicker,
|
||||
dbManagerDrawer,
|
||||
onClick,
|
||||
class: className = '',
|
||||
noText = false,
|
||||
@@ -38,7 +36,6 @@
|
||||
asset: Asset
|
||||
_resourceMetadata?: { resource_type?: string }
|
||||
s3FilePicker?: S3FilePicker
|
||||
dbManagerDrawer?: DbManagerDrawer
|
||||
onClick?: () => void
|
||||
class?: string
|
||||
noText?: boolean
|
||||
@@ -46,6 +43,8 @@
|
||||
btnClasses?: string
|
||||
disabled?: boolean
|
||||
} = $props()
|
||||
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
const assetUri = $derived(formatAsset(asset))
|
||||
</script>
|
||||
|
||||
|
||||
@@ -862,14 +862,6 @@
|
||||
|
||||
const mod = isMac() ? '⌘' : 'Ctrl+'
|
||||
|
||||
const undoShortcutSnippet = createRawSnippet(() => ({
|
||||
render: () => `<span class="ml-auto text-2xs text-tertiary">${mod}Z</span>`
|
||||
}))
|
||||
|
||||
const redoShortcutSnippet = createRawSnippet(() => ({
|
||||
render: () => `<span class="ml-auto text-2xs text-tertiary">${mod}⇧Z</span>`
|
||||
}))
|
||||
|
||||
function getMoreItems(): Item[] {
|
||||
return [
|
||||
...baseMenuItems,
|
||||
@@ -878,7 +870,7 @@
|
||||
icon: Undo,
|
||||
action: () => handleUndo(),
|
||||
disabled: $history.index === 0,
|
||||
extra: undoShortcutSnippet,
|
||||
shortcut: `${mod}Z`,
|
||||
separatorTop: baseMenuItems.length > 0
|
||||
},
|
||||
{
|
||||
@@ -886,7 +878,7 @@
|
||||
icon: Redo,
|
||||
action: () => handleRedo(),
|
||||
disabled: $history.index === $history.history.length - 1,
|
||||
extra: redoShortcutSnippet
|
||||
shortcut: `${mod}⇧Z`
|
||||
},
|
||||
{
|
||||
displayName: 'Tutorials',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService, WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount, untrack } from 'svelte'
|
||||
import AppConnect from './AppConnectDrawer.svelte'
|
||||
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
|
||||
@@ -174,7 +174,6 @@
|
||||
|
||||
let appConnect: AppConnect | undefined = $state()
|
||||
let resourceEditor: ResourceEditorDrawer | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let hovering = $state(false)
|
||||
let isDatatableSelected = $derived(value?.startsWith('datatable://') ?? false)
|
||||
</script>
|
||||
@@ -317,7 +316,6 @@
|
||||
class="mt-1"
|
||||
_resourceMetadata={{ resource_type: resourceType }}
|
||||
asset={{ kind: 'resource', path: value }}
|
||||
{dbManagerDrawer}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,9 @@
|
||||
(v) => {
|
||||
v.maxTs ? (filters.val.max_ts = new Date(v.maxTs)) : delete filters.val.max_ts
|
||||
v.minTs ? (filters.val.min_ts = new Date(v.minTs)) : delete filters.val.min_ts
|
||||
v.timeframe ? (filters.val.timeframe = v.timeframe) : delete filters.val.timeframe
|
||||
v.timeframe && v.timeframe !== 'Latest runs'
|
||||
? (filters.val.timeframe = v.timeframe)
|
||||
: delete filters.val.timeframe
|
||||
}
|
||||
)
|
||||
let timeframe = $derived(_timeframe.val)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
type Props = {
|
||||
s3FilePicker?: any | undefined
|
||||
dbManagerDrawer?: any | undefined
|
||||
resourceEditorDrawer?: ResourceEditorDrawer | undefined
|
||||
resourceDataCache: Record<string, string | undefined>
|
||||
asset: Asset
|
||||
@@ -18,7 +17,6 @@
|
||||
}
|
||||
let {
|
||||
s3FilePicker,
|
||||
dbManagerDrawer,
|
||||
resourceEditorDrawer,
|
||||
resourceDataCache,
|
||||
asset,
|
||||
@@ -71,7 +69,6 @@
|
||||
<ExploreAssetButton
|
||||
{asset}
|
||||
{s3FilePicker}
|
||||
{dbManagerDrawer}
|
||||
onClick={() => onClick?.()}
|
||||
noText
|
||||
_resourceMetadata={{ resource_type: resourceDataCacheValue }}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
} from './lib'
|
||||
import { untrack } from 'svelte'
|
||||
import { ResourceService, WorkspaceService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import Tooltip from '../meltComponents/Tooltip.svelte'
|
||||
import Tooltip2 from '../Tooltip.svelte'
|
||||
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
|
||||
@@ -48,7 +48,6 @@
|
||||
let blueBgDiv: HTMLDivElement | undefined = $state()
|
||||
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let resourceEditorDrawer: ResourceEditorDrawer | undefined = $state()
|
||||
let isOpen = $state(false)
|
||||
let resourceDataCache: Record<string, string | undefined> = $state({})
|
||||
@@ -209,7 +208,6 @@
|
||||
onClick={() => (isOpen = false)}
|
||||
{asset}
|
||||
{resourceDataCache}
|
||||
{dbManagerDrawer}
|
||||
{resourceEditorDrawer}
|
||||
{s3FilePicker}
|
||||
{ducklakeNotFound}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService, type Job } from '$lib/gen'
|
||||
import { inferAssets } from '$lib/infer'
|
||||
import { globalDbManagerDrawer, workspaceStore } from '$lib/stores'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { usePromise } from '$lib/svelte5Utils.svelte'
|
||||
import { pruneNullishArray, uniqueBy } from '$lib/utils'
|
||||
import ResourceEditorDrawer from '../ResourceEditorDrawer.svelte'
|
||||
@@ -64,7 +64,6 @@
|
||||
})
|
||||
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
|
||||
let resourceEditorDrawer: ResourceEditorDrawer | undefined = $state()
|
||||
</script>
|
||||
|
||||
@@ -86,7 +85,6 @@
|
||||
<AssetButtons
|
||||
{asset}
|
||||
{resourceDataCache}
|
||||
{dbManagerDrawer}
|
||||
{resourceEditorDrawer}
|
||||
{s3FilePicker}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import { z } from 'zod'
|
||||
import { useSearchParams } from '$lib/svelte5UtilsKit.svelte'
|
||||
import type { DbInput, DbType } from './dbTypes'
|
||||
import { isDbType } from './dbTypes'
|
||||
|
||||
/**
|
||||
* Single URL param `dbm` encodes the full DB manager state:
|
||||
* firstSegment~path~schema.table
|
||||
*
|
||||
* firstSegment:
|
||||
* datatable – database with datatable:// resource (resourceType always postgresql)
|
||||
* ducklake – ducklake connection
|
||||
* postgresql, mysql, … – regular database (the segment IS the resource type)
|
||||
*
|
||||
* schema.table (third segment, optional):
|
||||
* schema.table – both
|
||||
* .table – table only
|
||||
* schema. – schema only
|
||||
* (omitted) – neither
|
||||
*
|
||||
* Default schemas (omitted from URL, restored on parse):
|
||||
* datatable → public
|
||||
* ducklake → main
|
||||
*
|
||||
* Examples:
|
||||
* datatable~main~.customers (schema "public" implied)
|
||||
* ducklake~main~.orders (schema "main" implied)
|
||||
* postgresql~$res:u/user/my_pg~public.customers
|
||||
*/
|
||||
|
||||
const dbManagerSchema = z.object({
|
||||
dbm: z.string().nullable()
|
||||
})
|
||||
|
||||
interface ParsedDbm {
|
||||
type: 'database' | 'datatable' | 'ducklake'
|
||||
path: string
|
||||
resType?: string
|
||||
schema?: string
|
||||
table?: string
|
||||
}
|
||||
|
||||
function parseDbm(raw: unknown): ParsedDbm | null {
|
||||
if (!raw || typeof raw !== 'string') return null
|
||||
const parts = raw.split('~')
|
||||
if (parts.length < 2 || !parts[1]) return null
|
||||
|
||||
const firstSeg = parts[0]
|
||||
const path = parts[1]
|
||||
const schemaTable = parts[2] ?? ''
|
||||
|
||||
let type: ParsedDbm['type']
|
||||
let resType: string | undefined
|
||||
if (firstSeg === 'datatable') {
|
||||
type = 'datatable'
|
||||
} else if (firstSeg === 'ducklake') {
|
||||
type = 'ducklake'
|
||||
} else if (isDbType(firstSeg)) {
|
||||
type = 'database'
|
||||
resType = firstSeg
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
let schema: string | undefined
|
||||
let table: string | undefined
|
||||
if (schemaTable) {
|
||||
const dotIdx = schemaTable.indexOf('.')
|
||||
if (dotIdx === 0) {
|
||||
table = schemaTable.slice(1) || undefined
|
||||
} else if (dotIdx === schemaTable.length - 1) {
|
||||
schema = schemaTable.slice(0, -1) || undefined
|
||||
} else if (dotIdx > 0) {
|
||||
schema = schemaTable.slice(0, dotIdx)
|
||||
table = schemaTable.slice(dotIdx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore default schema when omitted for datatable/ducklake
|
||||
if (!schema && table && type in defaultSchemas) {
|
||||
schema = defaultSchemas[type]
|
||||
}
|
||||
|
||||
return { type, path, resType, schema, table }
|
||||
}
|
||||
|
||||
const defaultSchemas: Record<string, string> = { datatable: 'public', ducklake: 'main' }
|
||||
|
||||
function buildDbm(p: ParsedDbm): string {
|
||||
const firstSeg = p.type === 'database' ? p.resType! : p.type
|
||||
const schema = p.schema === defaultSchemas[p.type] ? undefined : p.schema
|
||||
let schemaTable = ''
|
||||
if (schema && p.table) {
|
||||
schemaTable = `${schema}.${p.table}`
|
||||
} else if (p.table) {
|
||||
schemaTable = `.${p.table}`
|
||||
} else if (schema) {
|
||||
schemaTable = `${schema}.`
|
||||
}
|
||||
return schemaTable ? `${firstSeg}~${p.path}~${schemaTable}` : `${firstSeg}~${p.path}`
|
||||
}
|
||||
|
||||
export interface DbManagerUriState {
|
||||
readonly input: DbInput | undefined
|
||||
readonly effectiveInput: DbInput | undefined
|
||||
readonly isDatatableInput: boolean
|
||||
selectedDatatable: string | undefined
|
||||
selectedSchema: string | undefined
|
||||
selectedTable: string | undefined
|
||||
readonly open: boolean
|
||||
openDrawer: (nInput: DbInput) => void
|
||||
closeDrawer: () => void
|
||||
}
|
||||
|
||||
export function useDbManagerUriState(): DbManagerUriState {
|
||||
const params = useSearchParams(dbManagerSchema)
|
||||
|
||||
const parsed = $derived(parseDbm(params.dbm))
|
||||
|
||||
let input: DbInput | undefined = $derived.by(() => {
|
||||
if (!parsed) return undefined
|
||||
if (parsed.type === 'ducklake') {
|
||||
return {
|
||||
type: 'ducklake' as const,
|
||||
ducklake: parsed.path,
|
||||
specificTable: parsed.table
|
||||
}
|
||||
}
|
||||
// datatable or database
|
||||
const resType = parsed.type === 'datatable' ? 'postgresql' : parsed.resType
|
||||
if (!isDbType(resType ?? undefined)) return undefined
|
||||
return {
|
||||
type: 'database' as const,
|
||||
resourceType: resType as DbType,
|
||||
resourcePath: parsed.type === 'datatable' ? `datatable://${parsed.path}` : parsed.path,
|
||||
specificSchema: parsed.schema,
|
||||
specificTable: parsed.table
|
||||
}
|
||||
})
|
||||
|
||||
const isDatatableInput = $derived(parsed?.type === 'datatable')
|
||||
|
||||
function updateField(updates: Partial<ParsedDbm>) {
|
||||
const p = parseDbm(params.dbm)
|
||||
if (!p) return
|
||||
Object.assign(p, updates)
|
||||
params.dbm = buildDbm(p)
|
||||
}
|
||||
|
||||
function openDrawer(nInput: DbInput) {
|
||||
if (nInput.type === 'database') {
|
||||
const isDatatable = nInput.resourcePath.startsWith('datatable://')
|
||||
params.dbm = buildDbm({
|
||||
type: isDatatable ? 'datatable' : 'database',
|
||||
path: isDatatable ? nInput.resourcePath.slice('datatable://'.length) : nInput.resourcePath,
|
||||
resType: isDatatable ? undefined : nInput.resourceType,
|
||||
schema: nInput.specificSchema,
|
||||
table: nInput.specificTable
|
||||
})
|
||||
} else {
|
||||
params.dbm = buildDbm({
|
||||
type: 'ducklake',
|
||||
path: nInput.ducklake,
|
||||
table: nInput.specificTable
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
params.dbm = null
|
||||
}
|
||||
|
||||
return {
|
||||
get input() {
|
||||
return input
|
||||
},
|
||||
get effectiveInput() {
|
||||
return input
|
||||
},
|
||||
get isDatatableInput() {
|
||||
return isDatatableInput
|
||||
},
|
||||
get selectedDatatable() {
|
||||
return parsed?.type === 'datatable' ? parsed.path : undefined
|
||||
},
|
||||
set selectedDatatable(v: string | undefined) {
|
||||
if (v) updateField({ path: v })
|
||||
},
|
||||
get selectedSchema() {
|
||||
return parsed?.schema
|
||||
},
|
||||
set selectedSchema(v: string | undefined) {
|
||||
updateField({ schema: v })
|
||||
},
|
||||
get selectedTable() {
|
||||
return parsed?.table
|
||||
},
|
||||
set selectedTable(v: string | undefined) {
|
||||
updateField({ table: v })
|
||||
},
|
||||
get open() {
|
||||
return !!input
|
||||
},
|
||||
openDrawer,
|
||||
closeDrawer
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,11 @@
|
||||
import FlowResult from './FlowResult.svelte'
|
||||
import type { StateStore } from '$lib/utils'
|
||||
import FlowSelectionPanel from './FlowSelectionPanel.svelte'
|
||||
import {
|
||||
resolveSelectedModuleIds,
|
||||
locateModules,
|
||||
areContiguousSiblings
|
||||
} from '../multiSelectUtils'
|
||||
|
||||
interface Props {
|
||||
noEditor?: boolean
|
||||
@@ -89,10 +94,29 @@
|
||||
$effect(() => {
|
||||
computeMissingInputWarnings(flowStore, flowStateStore.val, flowInputsStore)
|
||||
})
|
||||
|
||||
// Derived state for multi-select operations in the side panel
|
||||
let resolvedModuleIds = $derived(
|
||||
resolveSelectedModuleIds(selectionManager.selectedIds, flowStore.val.value.modules ?? [])
|
||||
)
|
||||
let canMoveSelected = $derived(
|
||||
resolvedModuleIds.length > 0 &&
|
||||
areContiguousSiblings(
|
||||
locateModules(resolvedModuleIds, flowStore.val.value.modules ?? [])
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if selectionManager && selectionManager.selectedIds.length > 1}
|
||||
<FlowSelectionPanel {selectionManager} {noEditor} />
|
||||
<FlowSelectionPanel
|
||||
{selectionManager}
|
||||
{noEditor}
|
||||
onDeleteSelected={() => flowModuleSchemaMap?.deleteMultiple(resolvedModuleIds)}
|
||||
onDuplicateSelected={() => flowModuleSchemaMap?.duplicateMultiple(resolvedModuleIds)}
|
||||
onMoveSelected={() => flowModuleSchemaMap?.moveMultiple(resolvedModuleIds)}
|
||||
{canMoveSelected}
|
||||
resolvedCount={resolvedModuleIds.length}
|
||||
/>
|
||||
{:else if selectedId?.startsWith('settings')}
|
||||
<FlowSettings {enableAi} {noEditor} />
|
||||
{:else if selectedId === 'Input'}
|
||||
|
||||
@@ -425,7 +425,6 @@
|
||||
on:blur={() => {
|
||||
iteratorFieldFocused = false
|
||||
}}
|
||||
autofocus
|
||||
lang="javascript"
|
||||
bind:code={mod.value.iterator.expr}
|
||||
class="h-full"
|
||||
|
||||
@@ -2,14 +2,29 @@
|
||||
import FlowCard from '../common/FlowCard.svelte'
|
||||
import type { SelectionManager } from '$lib/components/graph/selectionUtils.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import DropdownV2 from '$lib/components/DropdownV2.svelte'
|
||||
import { getNoteEditorContext } from '$lib/components/graph/noteEditor.svelte'
|
||||
import { StickyNote } from 'lucide-svelte'
|
||||
import { StickyNote, Move, Copy, Trash2 } from 'lucide-svelte'
|
||||
import type { Item } from '$lib/utils'
|
||||
|
||||
interface Props {
|
||||
selectionManager: SelectionManager
|
||||
noEditor: boolean
|
||||
onDeleteSelected?: () => void
|
||||
onDuplicateSelected?: () => void
|
||||
onMoveSelected?: () => void
|
||||
canMoveSelected?: boolean
|
||||
resolvedCount?: number
|
||||
}
|
||||
let { selectionManager, noEditor }: Props = $props()
|
||||
let {
|
||||
selectionManager,
|
||||
noEditor,
|
||||
onDeleteSelected,
|
||||
onDuplicateSelected,
|
||||
onMoveSelected,
|
||||
canMoveSelected = false,
|
||||
resolvedCount = 0
|
||||
}: Props = $props()
|
||||
|
||||
const noteEditorContext = getNoteEditorContext()
|
||||
|
||||
@@ -19,20 +34,44 @@
|
||||
noteEditorContext.noteEditor.createGroupNote(selectionManager.selectedIds)
|
||||
}
|
||||
}
|
||||
|
||||
let menuItems: Item[] = $derived([
|
||||
{
|
||||
displayName: 'Move',
|
||||
icon: Move,
|
||||
action: () => onMoveSelected?.(),
|
||||
disabled: !canMoveSelected
|
||||
},
|
||||
{
|
||||
displayName: 'Duplicate',
|
||||
icon: Copy,
|
||||
action: () => onDuplicateSelected?.()
|
||||
},
|
||||
{
|
||||
displayName: `Delete (${resolvedCount})`,
|
||||
icon: Trash2,
|
||||
type: 'delete',
|
||||
action: () => onDeleteSelected?.()
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<FlowCard {noEditor} title="Multiple Selection">
|
||||
{#snippet action()}
|
||||
<Button
|
||||
onClick={addGroupNote}
|
||||
disabled={!noteEditorContext?.noteEditor || selectionManager.selectedIds.length === 0}
|
||||
startIcon={{ icon: StickyNote }}
|
||||
>
|
||||
Create group note
|
||||
</Button>
|
||||
<div class="flex gap-1 items-center">
|
||||
<Button
|
||||
onClick={addGroupNote}
|
||||
disabled={!noteEditorContext?.noteEditor || selectionManager.selectedIds.length === 0}
|
||||
startIcon={{ icon: StickyNote }}
|
||||
>
|
||||
Create group note
|
||||
</Button>
|
||||
{#if resolvedCount > 0}
|
||||
<DropdownV2 items={menuItems} />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
<div class="px-4">
|
||||
<p class="text-xs text-secondary mb-4">{selectionManager.selectedIds.length} nodes selected</p>
|
||||
<div class="space-y-2 mb-4">
|
||||
{#each selectionManager.selectedIds as nodeId}
|
||||
<div class="text-sm px-2 py-1 bg-surface rounded border">
|
||||
|
||||
@@ -17,3 +17,15 @@ export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
|
||||
}, 0)
|
||||
return numberToChars(max)
|
||||
}
|
||||
|
||||
// Computes a copy id like "a2", "a3", etc. based on the original id
|
||||
export function copyId(originalId: string, flowState: FlowState, fullFlow: OpenFlow): string {
|
||||
const allIds = new Set(dfs(fullFlow.value.modules, (fm) => fm.id).concat(Object.keys(flowState)))
|
||||
for (let n = 2; n < 10000; n++) {
|
||||
const candidate = `${originalId}${n}`
|
||||
if (!allIds.has(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return `${originalId}10000`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
import { preventDefault, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { classNames, type StateStore } from '$lib/utils'
|
||||
import DropdownV2 from '$lib/components/DropdownV2.svelte'
|
||||
import { classNames, type Item, type StateStore } from '$lib/utils'
|
||||
import {
|
||||
Bed,
|
||||
Database,
|
||||
Gauge,
|
||||
Move,
|
||||
EllipsisVertical,
|
||||
PhoneIncoming,
|
||||
Repeat,
|
||||
Square,
|
||||
@@ -20,7 +21,7 @@
|
||||
Timer,
|
||||
Maximize2
|
||||
} from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -48,6 +49,7 @@
|
||||
import type { ModuleActionInfo } from '$lib/components/flows/flowDiff'
|
||||
import DiffActionBar from './DiffActionBar.svelte'
|
||||
import { getGraphContext } from '$lib/components/graph/graphContext'
|
||||
import MoveHandleButton from '$lib/components/graph/MoveHandleButton.svelte'
|
||||
|
||||
interface Props {
|
||||
selected?: boolean
|
||||
@@ -69,7 +71,6 @@
|
||||
id?: string | undefined
|
||||
label: string
|
||||
path?: string
|
||||
modType?: string | undefined
|
||||
nodeState?: FlowNodeState
|
||||
concurrency?: boolean
|
||||
// TODO: Implement for this one. See how concurrency is implemented.
|
||||
@@ -89,6 +90,7 @@
|
||||
isOwner?: boolean
|
||||
enableTestRun?: boolean
|
||||
maximizeSubflow?: () => void
|
||||
menuItems?: Item[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -106,7 +108,6 @@
|
||||
id = undefined,
|
||||
label,
|
||||
path = '',
|
||||
modType = undefined,
|
||||
nodeState,
|
||||
concurrency = false,
|
||||
debouncing = false,
|
||||
@@ -123,7 +124,8 @@
|
||||
onEditInput,
|
||||
flowJob,
|
||||
enableTestRun = false,
|
||||
maximizeSubflow = undefined
|
||||
maximizeSubflow = undefined,
|
||||
menuItems = undefined
|
||||
}: Props = $props()
|
||||
|
||||
// AI action colors take priority over execution state
|
||||
@@ -138,6 +140,9 @@
|
||||
const diffManager = flowGraphContext?.diffManager
|
||||
const moveManager = flowGraphContext?.moveManager
|
||||
|
||||
// Hide per-node action buttons when multiple nodes are selected (multi-select mode)
|
||||
let isMultiSelected = $derived((flowGraphContext?.selectionManager?.selectedIds?.length ?? 0) > 1)
|
||||
|
||||
let pickableIds: Record<string, any> | undefined = $state(undefined)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -161,6 +166,7 @@
|
||||
let outputPicker: OutputPicker | undefined = $state(undefined)
|
||||
let testJob: any | undefined = $state(undefined)
|
||||
let outputPickerBarOpen = $state(false)
|
||||
let dropdownOpen = $state(false)
|
||||
|
||||
let flowStateStore = $derived(flowEditorContext?.flowStateStore)
|
||||
|
||||
@@ -188,42 +194,6 @@
|
||||
|
||||
let isDragging = $derived(!!moveManager?.dragging)
|
||||
|
||||
// --- Drag handle logic ---
|
||||
let dragCleanup: (() => void) | undefined
|
||||
|
||||
function onMovePointerDown(e: Event) {
|
||||
const pe = e as PointerEvent
|
||||
const startX = pe.clientX
|
||||
const startY = pe.clientY
|
||||
let didDrag = false
|
||||
|
||||
function onMovePointer(me: PointerEvent) {
|
||||
const dx = me.clientX - startX
|
||||
const dy = me.clientY - startY
|
||||
if (!didDrag && Math.sqrt(dx * dx + dy * dy) > 5) {
|
||||
didDrag = true
|
||||
if (moveManager && id) {
|
||||
moveManager.startDrag(id, startX, startY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onUp() {
|
||||
document.removeEventListener('pointermove', onMovePointer)
|
||||
document.removeEventListener('pointerup', onUp)
|
||||
dragCleanup = undefined
|
||||
if (!didDrag) {
|
||||
dispatch('move')
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', onMovePointer)
|
||||
document.addEventListener('pointerup', onUp)
|
||||
dragCleanup = onUp
|
||||
}
|
||||
|
||||
onDestroy(() => dragCleanup?.())
|
||||
|
||||
const outputPickerVisible = $derived(
|
||||
editMode && (isConnectingCandidate || alwaysShowOutputPicker) && !!id && !isDragging
|
||||
)
|
||||
@@ -467,6 +437,7 @@
|
||||
{deletable}
|
||||
{bold}
|
||||
bind:editId
|
||||
disableEditId={isMultiSelected}
|
||||
{hover}
|
||||
{colorClasses}
|
||||
>
|
||||
@@ -475,7 +446,7 @@
|
||||
{/snippet}
|
||||
</FlowModuleSchemaItemViewer>
|
||||
|
||||
{#if outputPickerVisible}
|
||||
{#if outputPickerVisible && !isMultiSelected}
|
||||
<OutputPicker
|
||||
bind:this={outputPicker}
|
||||
{selected}
|
||||
@@ -518,50 +489,6 @@
|
||||
{@render buttonMaximizeSubflow?.()}
|
||||
{/if}
|
||||
|
||||
{#if id !== 'preprocessor'}
|
||||
<!-- The `style="will-change: transform;"` fixes a bug in Safari where the close and move
|
||||
and delete buttons would get clipped (unless an animation is running) -->
|
||||
<div
|
||||
class={twMerge('absolute -translate-y-[100%] top-2 right-4 h-7 p-1 min-w-7')}
|
||||
style="will-change: transform;"
|
||||
>
|
||||
<button
|
||||
class={twMerge(
|
||||
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary cursor-grab',
|
||||
hover || selected ? 'block' : '!hidden',
|
||||
'shadow-md rounded-md',
|
||||
'group-hover:block',
|
||||
'touch-none'
|
||||
)}
|
||||
onpointerdown={stopPropagation(preventDefault(onMovePointerDown))}
|
||||
title="Move"
|
||||
>
|
||||
<Move size={12} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="absolute -translate-y-[100%] top-2 -right-2 h-7 p-1 min-w-7"
|
||||
style="will-change: transform;"
|
||||
>
|
||||
<button
|
||||
class={twMerge(
|
||||
'trash center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-red-400 hover:text-white p-1',
|
||||
selected || hover ? 'block' : '!hidden',
|
||||
'group-hover:block',
|
||||
'shadow-md rounded-md'
|
||||
)}
|
||||
title="Delete"
|
||||
onclick={stopPropagation(
|
||||
preventDefault((event) => dispatch('delete', { id, type: modType }))
|
||||
)}
|
||||
onpointerdown={stopPropagation(preventDefault(() => {}))}
|
||||
>
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if (id && Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}).length > 0) || Boolean(warningMessage)}
|
||||
<Popover
|
||||
style="will-change: transform;"
|
||||
@@ -591,6 +518,52 @@
|
||||
<TriangleAlert size={12} strokeWidth={2} />
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
{#if !isMultiSelected && id !== 'preprocessor' && moveManager && id}
|
||||
<div
|
||||
class="absolute -translate-y-[100%] top-2 right-5 h-7 p-1 min-w-7"
|
||||
style="will-change: transform;"
|
||||
>
|
||||
<MoveHandleButton
|
||||
{moveManager}
|
||||
moduleId={id}
|
||||
singleNode
|
||||
visible={hover || selected || dropdownOpen}
|
||||
onClickMove={() => dispatch('move')}
|
||||
class="trash group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isMultiSelected && id !== 'preprocessor' && menuItems && menuItems.length > 0}
|
||||
<div
|
||||
class="absolute -translate-y-[100%] top-2 -right-2 h-7 p-1 min-w-7"
|
||||
style="will-change: transform;"
|
||||
>
|
||||
<DropdownV2
|
||||
items={menuItems}
|
||||
placement="bottom-end"
|
||||
bind:open={dropdownOpen}
|
||||
fixedHeight={false}
|
||||
usePointerDownOutside
|
||||
>
|
||||
{#snippet buttonReplacement()}
|
||||
<button
|
||||
class={twMerge(
|
||||
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary',
|
||||
hover || selected || dropdownOpen ? 'block' : '!hidden',
|
||||
'shadow-md rounded-md',
|
||||
'group-hover:block'
|
||||
)}
|
||||
onpointerdown={stopPropagation(preventDefault(() => {}))}
|
||||
title="Actions"
|
||||
>
|
||||
<EllipsisVertical size={12} />
|
||||
</button>
|
||||
{/snippet}
|
||||
</DropdownV2>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if maximizeSubflow !== undefined}
|
||||
{@render buttonMaximizeSubflow?.()}
|
||||
{/if}
|
||||
@@ -603,7 +576,7 @@
|
||||
onmouseenter={() => (hover = true)}
|
||||
onmouseleave={() => (hover = false)}
|
||||
>
|
||||
{#if (hover || selected || testRunDropdownOpen) && outputPickerVisible}
|
||||
{#if !isMultiSelected && (hover || selected || testRunDropdownOpen) && outputPickerVisible}
|
||||
<div transition:fade={{ duration: 100 }}>
|
||||
{#if !testIsLoading}
|
||||
<Button
|
||||
@@ -655,13 +628,13 @@
|
||||
</div>
|
||||
|
||||
{#snippet buttonMaximizeSubflow()}
|
||||
<div class="absolute -translate-y-[100%] top-2 right-10 h-7 p-1">
|
||||
<div class="absolute -translate-y-[100%] top-2 right-12 h-7 p-1">
|
||||
<button
|
||||
title="Expand subflow"
|
||||
class={twMerge(
|
||||
'center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary p-1',
|
||||
'shadow-md rounded-md',
|
||||
hover || selected ? 'opacity-100' : 'opacity-50'
|
||||
!isMultiSelected && (hover || selected) ? 'opacity-100' : 'opacity-50'
|
||||
)}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
deletable?: boolean
|
||||
bold?: boolean
|
||||
editId?: boolean
|
||||
disableEditId?: boolean
|
||||
hover?: boolean
|
||||
colorClasses?: FlowNodeColorClasses
|
||||
icon?: import('svelte').Snippet
|
||||
@@ -28,6 +29,7 @@
|
||||
deletable = false,
|
||||
bold = false,
|
||||
editId = $bindable(false),
|
||||
disableEditId = false,
|
||||
hover = false,
|
||||
colorClasses,
|
||||
icon,
|
||||
@@ -74,16 +76,18 @@
|
||||
)}
|
||||
baseClass={twMerge('!px-1')}
|
||||
title={id}
|
||||
clickable
|
||||
onclick={(e) => {
|
||||
e?.preventDefault()
|
||||
e?.stopPropagation()
|
||||
editId = !editId
|
||||
onclick?.()
|
||||
}}
|
||||
clickable={!disableEditId}
|
||||
onclick={disableEditId
|
||||
? undefined
|
||||
: (e) => {
|
||||
e?.preventDefault()
|
||||
e?.stopPropagation()
|
||||
editId = !editId
|
||||
onclick?.()
|
||||
}}
|
||||
>
|
||||
<span class="max-w-full text-2xs truncate flex items-center">
|
||||
{#if editId || (hover && deletable)}
|
||||
{#if !disableEditId && (editId || (hover && deletable))}
|
||||
<span transition:slide={{ axis: 'x', duration: 100 }}>
|
||||
<Pencil size={10} class="mr-1" />
|
||||
</span>
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
import { emptyFlowModuleState } from '../utils.svelte'
|
||||
|
||||
import { dfs } from '../dfs'
|
||||
import { nextId, copyId } from '../flowModuleNextId'
|
||||
import { push } from '$lib/history.svelte'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import Portal from '$lib/components/Portal.svelte'
|
||||
|
||||
import { getDependentComponents } from '../flowExplorer'
|
||||
import { locateModules, groupByParent } from '../multiSelectUtils'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
import FlowTutorials from '$lib/components/FlowTutorials.svelte'
|
||||
@@ -345,6 +347,80 @@
|
||||
noteMode = !noteMode
|
||||
}
|
||||
|
||||
export function deleteMultiple(ids: string[]) {
|
||||
const deletingSet = new Set(ids)
|
||||
const allDeps: Record<string, string[]> = {}
|
||||
for (const id of ids) {
|
||||
const deps = getDependentComponents(id, flowStore.val)
|
||||
for (const [depId, exprs] of Object.entries(deps)) {
|
||||
if (!deletingSet.has(depId)) {
|
||||
allDeps[depId] = [...(allDeps[depId] ?? []), ...exprs]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cb = () => {
|
||||
push(history, flowStore.val)
|
||||
for (const id of ids) {
|
||||
removeAtId(flowStore.val.value.modules, id)
|
||||
delete flowStateStore.val[id]
|
||||
}
|
||||
selectionManager.clearSelection()
|
||||
refreshStateStore(flowStore)
|
||||
}
|
||||
|
||||
if (Object.keys(allDeps).length > 0) {
|
||||
dependents = allDeps
|
||||
deleteCallback = cb
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
export function duplicateMultiple(ids: string[]) {
|
||||
const locations = locateModules(ids, flowStore.val.value.modules)
|
||||
const groups = groupByParent(locations)
|
||||
|
||||
push(history, flowStore.val)
|
||||
|
||||
const allCloneIds: string[] = []
|
||||
|
||||
for (const group of groups) {
|
||||
const sorted = [...group].sort((a, b) => a.index - b.index)
|
||||
const parentArr = sorted[0].parentArray
|
||||
const lastIndex = sorted[sorted.length - 1].index
|
||||
|
||||
const clones: FlowModule[] = []
|
||||
for (const loc of sorted) {
|
||||
const original = parentArr[loc.index]
|
||||
const clone: FlowModule = $state.snapshot(original)
|
||||
|
||||
clone.id = copyId(original.id, flowStateStore.val, flowStore.val)
|
||||
flowStateStore.val[clone.id] = emptyFlowModuleState()
|
||||
|
||||
dfs([clone], (mod) => {
|
||||
if (mod.id !== clone.id) {
|
||||
const newModId = nextId(flowStateStore.val, flowStore.val)
|
||||
mod.id = newModId
|
||||
flowStateStore.val[newModId] = emptyFlowModuleState()
|
||||
}
|
||||
})
|
||||
|
||||
clones.push(clone)
|
||||
allCloneIds.push(clone.id)
|
||||
}
|
||||
|
||||
parentArr.splice(lastIndex + 1, 0, ...clones)
|
||||
}
|
||||
|
||||
refreshStateStore(flowStore)
|
||||
selectionManager.selectByIds(allCloneIds)
|
||||
}
|
||||
|
||||
export function moveMultiple(ids: string[]) {
|
||||
moveManager.toggleMovingMultiple(ids)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
generateStep: { moduleId: string; instructions: string; lang: ScriptLang }
|
||||
change: void
|
||||
@@ -533,18 +609,37 @@
|
||||
if (flowStore.val.value.modules && Array.isArray(flowStore.val.value.modules)) {
|
||||
await tick()
|
||||
if (moveManager.movingModuleId) {
|
||||
// console.log('modules', modules, movingModules, movingModule)
|
||||
push(history, flowStore.val)
|
||||
let indexToRemove = originalModules.findIndex((m) => moveManager.movingModuleId == m.id)
|
||||
|
||||
let [removedModule] = originalModules.splice(indexToRemove, 1)
|
||||
// When moving within the same array, removal shifts subsequent indices down by 1
|
||||
let insertIndex = detail.index
|
||||
if (originalModules === targetModules && indexToRemove < detail.index) {
|
||||
insertIndex -= 1
|
||||
if (!originalModules || !targetModules) {
|
||||
moveManager.clearMoving()
|
||||
return
|
||||
}
|
||||
if (moveManager.movingIds && moveManager.movingIds.length > 1) {
|
||||
// Multi-move: splice out all moving modules from their parent, insert at target
|
||||
const firstIndex = originalModules.findIndex(
|
||||
(m) => m.id === moveManager.movingIds?.[0]
|
||||
)
|
||||
const removedModules = originalModules.splice(
|
||||
firstIndex,
|
||||
moveManager.movingIds.length
|
||||
)
|
||||
let insertIndex = detail.index
|
||||
if (originalModules === targetModules && firstIndex < detail.index) {
|
||||
insertIndex -= moveManager.movingIds.length
|
||||
}
|
||||
targetModules.splice(insertIndex, 0, ...removedModules)
|
||||
selectionManager.selectByIds(removedModules.map((m) => m.id))
|
||||
} else {
|
||||
let indexToRemove = originalModules.findIndex((m) => moveManager.movingModuleId == m.id)
|
||||
let [removedModule] = originalModules.splice(indexToRemove, 1)
|
||||
// When moving within the same array, removal shifts subsequent indices down by 1
|
||||
let insertIndex = detail.index
|
||||
if (originalModules === targetModules && indexToRemove < detail.index) {
|
||||
insertIndex -= 1
|
||||
}
|
||||
targetModules.splice(insertIndex, 0, removedModule)
|
||||
selectionManager.selectId(removedModule.id)
|
||||
}
|
||||
targetModules.splice(insertIndex, 0, removedModule)
|
||||
selectionManager.selectId(removedModule.id)
|
||||
moveManager.clearMoving()
|
||||
} else {
|
||||
if (detail.isPreprocessor) {
|
||||
@@ -678,6 +773,41 @@
|
||||
onMove={(id) => {
|
||||
moveManager.toggleMoving(id)
|
||||
}}
|
||||
onDuplicate={(id) => {
|
||||
let targetModules: FlowModule[] | undefined
|
||||
let targetIndex: number = -1
|
||||
|
||||
dfs(flowStore.val.value.modules, (mod, modules) => {
|
||||
const idx = modules.findIndex((m) => m.id === id)
|
||||
if (idx !== -1) {
|
||||
targetModules = modules
|
||||
targetIndex = idx
|
||||
}
|
||||
})
|
||||
|
||||
if (!targetModules || targetIndex === -1) return
|
||||
|
||||
push(history, flowStore.val)
|
||||
|
||||
const original = targetModules[targetIndex]
|
||||
const clone: FlowModule = $state.snapshot(original)
|
||||
|
||||
// Assign copy id to the clone, and fresh ids to nested modules
|
||||
clone.id = copyId(original.id, flowStateStore.val, flowStore.val)
|
||||
flowStateStore.val[clone.id] = emptyFlowModuleState()
|
||||
|
||||
dfs([clone], (mod) => {
|
||||
if (mod.id !== clone.id) {
|
||||
const newModId = nextId(flowStateStore.val, flowStore.val)
|
||||
mod.id = newModId
|
||||
flowStateStore.val[newModId] = emptyFlowModuleState()
|
||||
}
|
||||
})
|
||||
|
||||
targetModules.splice(targetIndex + 1, 0, clone)
|
||||
refreshStateStore(flowStore)
|
||||
selectionManager.selectId(clone.id)
|
||||
}}
|
||||
onUpdateMock={(detail) => {
|
||||
let module = findModuleById(detail.id)
|
||||
module.mock = $state.snapshot(detail.mock)
|
||||
@@ -696,6 +826,10 @@
|
||||
}
|
||||
}}
|
||||
multiSelectEnabled
|
||||
movingIds={moveManager.movingIds}
|
||||
onDeleteMultiple={deleteMultiple}
|
||||
onDuplicateMultiple={duplicateMultiple}
|
||||
onMoveMultiple={moveMultiple}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||