mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-26 08:02:31 +00:00
Merge branch 'main' into dieri/triggers-queue-mode
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [1.576.0](https://github.com/windmill-labs/windmill/compare/v1.575.4...v1.576.0) (2025-11-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for switch and attributes in pwsh params ([#7143](https://github.com/windmill-labs/windmill/issues/7143)) ([c16bef8](https://github.com/windmill-labs/windmill/commit/c16bef8f296645ff873f9d8d28e3dcb50a65e304))
|
||||
* **ai:** handle aws bedrock as provider ([#7131](https://github.com/windmill-labs/windmill/issues/7131)) ([30eb9aa](https://github.com/windmill-labs/windmill/commit/30eb9aae25eeb563ad119ef93f3ff1ab17c66d75))
|
||||
* webhook by flow version ([#7062](https://github.com/windmill-labs/windmill/issues/7062)) ([09cdfb4](https://github.com/windmill-labs/windmill/commit/09cdfb4556748903dc5bbf53ef3356ac97c57d90))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use proper TLS connector for DuckLake instance catalog setup ([#7138](https://github.com/windmill-labs/windmill/issues/7138)) ([cf36fe3](https://github.com/windmill-labs/windmill/commit/cf36fe3bb1beec80fa84dc342a8a38cc7369bc4d))
|
||||
|
||||
## [1.575.4](https://github.com/windmill-labs/windmill/compare/v1.575.3...v1.575.4) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7cab8475996f0abd7ca7888b460bd3faf3bef6e34200c258e3704981817c241e"
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return, \n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, \n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, \n flow.tag, \n flow.dedicated_worker, \n flow.on_behalf_of_email, \n flow.edited_by\n FROM \n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE \n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "has_preprocessor",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "dedicated_worker",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b82f97f2a858e20b1fb6d9b0b3eab5bab2bafa91689eac5073c1678c338afb27"
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "dedicated_worker",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "has_preprocessor",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e1409c67b93881cf68f6ac9c8bae0856cf426c7e7860c8b5b799972baa8e6945"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1",
|
||||
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n id = $1\n AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -25,5 +25,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e2474b7855c8b08f927f2b987421e773e537a8eb0a113477764bbab14e1f3a3d"
|
||||
"hash": "f0c9670c27d9a3b8e8f316fd28f7d3ede2ecc3225ff2879ff5dfd59c4ef236d5"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM flow_version WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fc9753f501974c4b570d710c7621ff1f6787a852bb27acf49593af963a6aacca"
|
||||
}
|
||||
Generated
+66
-65
@@ -199,22 +199,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1026,7 +1026,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
@@ -1182,7 +1182,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
@@ -1569,7 +1569,7 @@ dependencies = [
|
||||
"hex",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-named-pipe",
|
||||
"hyper-util",
|
||||
"hyperlocal",
|
||||
@@ -1799,9 +1799,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1945,9 +1945,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.45"
|
||||
version = "1.2.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
|
||||
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -3625,7 +3625,7 @@ dependencies = [
|
||||
"hickory-resolver",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@@ -3713,7 +3713,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"hyper 0.14.32",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"itertools 0.10.5",
|
||||
"memmem",
|
||||
@@ -3906,7 +3906,7 @@ dependencies = [
|
||||
"hkdf",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"idna",
|
||||
"indexmap 2.11.1",
|
||||
@@ -4177,7 +4177,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 0.14.32",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -4231,7 +4231,7 @@ dependencies = [
|
||||
"deno_error",
|
||||
"deno_tls",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"log",
|
||||
@@ -4361,7 +4361,7 @@ dependencies = [
|
||||
"h2 0.4.12",
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"once_cell",
|
||||
"rustls-tokio-stream",
|
||||
@@ -5225,7 +5225,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
@@ -5292,9 +5292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
@@ -6604,9 +6604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.0"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -6635,7 +6635,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 1.3.1",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
@@ -6652,7 +6652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -6683,7 +6683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rustls 0.23.29",
|
||||
@@ -6701,7 +6701,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
|
||||
dependencies = [
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -6729,7 +6729,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
@@ -6750,7 +6750,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
@@ -6771,7 +6771,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -7419,7 +7419,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-http-proxy",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-timeout",
|
||||
@@ -8946,7 +8946,7 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"httparse",
|
||||
"humantime",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"itertools 0.14.0",
|
||||
"md-5 0.10.6",
|
||||
"parking_lot 0.12.5",
|
||||
@@ -10712,7 +10712,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
@@ -10772,7 +10772,7 @@ dependencies = [
|
||||
"futures",
|
||||
"getrandom 0.2.16",
|
||||
"http 1.3.1",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"parking_lot 0.11.2",
|
||||
"reqwest 0.12.24",
|
||||
"reqwest-middleware",
|
||||
@@ -13945,7 +13945,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"hyper-timeout",
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
@@ -15148,7 +15148,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aws-sdk-config",
|
||||
@@ -15208,7 +15208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15251,7 +15251,7 @@ dependencies = [
|
||||
"hf-hub",
|
||||
"hmac",
|
||||
"http 1.3.1",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"indexmap 2.11.1",
|
||||
"itertools 0.14.0",
|
||||
"jsonwebtoken 8.3.0",
|
||||
@@ -15269,6 +15269,7 @@ dependencies = [
|
||||
"pg_escape",
|
||||
"pin-project",
|
||||
"postgres-native-tls 0.5.0",
|
||||
"postgres-native-tls 0.5.1",
|
||||
"prometheus",
|
||||
"quick_cache",
|
||||
"rand 0.9.0",
|
||||
@@ -15328,7 +15329,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -15343,7 +15344,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -15357,7 +15358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -15376,7 +15377,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15404,7 +15405,7 @@ dependencies = [
|
||||
"globset",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hyper 1.8.0",
|
||||
"hyper 1.8.1",
|
||||
"indexmap 2.11.1",
|
||||
"itertools 0.14.0",
|
||||
"jsonwebtoken 8.3.0",
|
||||
@@ -15461,7 +15462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -15476,7 +15477,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -15500,7 +15501,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -15512,7 +15513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -15521,7 +15522,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15533,7 +15534,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15545,7 +15546,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -15557,7 +15558,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15569,7 +15570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15581,7 +15582,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -15592,7 +15593,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15603,7 +15604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -15615,7 +15616,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15638,7 +15639,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15652,7 +15653,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -15669,7 +15670,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15683,7 +15684,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15701,7 +15702,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -15712,7 +15713,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15747,7 +15748,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -15757,7 +15758,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
90ef44ceb8220e7e54a2ac40756c96fe003d8695
|
||||
0772f05a60809e9d9a11c25173a246724357520c
|
||||
@@ -46,8 +46,6 @@ pub fn parse_powershell_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?\r?$"#).unwrap();
|
||||
|
||||
static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[([\w\[\]]+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?\r?"#).unwrap();
|
||||
}
|
||||
|
||||
fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
@@ -98,10 +96,12 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti
|
||||
let lower_code = code.to_lowercase();
|
||||
let param_start = lower_code.find("param")?;
|
||||
|
||||
// Verify that only comments and whitespace appear before "param"
|
||||
// Verify that only comments, whitespace, and [CmdletBinding()] appear before "param"
|
||||
let before_param = &code[..param_start];
|
||||
let mut chars = before_param.chars().peekable();
|
||||
let mut in_block_comment = false;
|
||||
let mut in_attribute_bracket = false;
|
||||
let mut bracket_depth = 0;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if in_block_comment {
|
||||
@@ -110,6 +110,19 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti
|
||||
chars.next(); // consume '>'
|
||||
in_block_comment = false;
|
||||
}
|
||||
} else if in_attribute_bracket {
|
||||
// Track bracket depth to handle nested brackets/parens in attributes
|
||||
match ch {
|
||||
'[' => bracket_depth += 1,
|
||||
']' => {
|
||||
bracket_depth -= 1;
|
||||
if bracket_depth == 0 {
|
||||
in_attribute_bracket = false;
|
||||
}
|
||||
}
|
||||
// Allow parentheses inside attributes (e.g., [CmdletBinding()])
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
match ch {
|
||||
// Start of block comment: <#
|
||||
@@ -126,6 +139,11 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
// Start of attribute bracket (e.g., [CmdletBinding()])
|
||||
'[' => {
|
||||
in_attribute_bracket = true;
|
||||
bracket_depth = 1;
|
||||
}
|
||||
// Whitespace is allowed
|
||||
c if c.is_whitespace() => {}
|
||||
// Any other character means there's code before param
|
||||
@@ -134,8 +152,8 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti
|
||||
}
|
||||
}
|
||||
|
||||
// If we're still in a block comment at the end, it's unclosed - invalid
|
||||
if in_block_comment {
|
||||
// If we're still in a block comment or unclosed attribute bracket, it's invalid
|
||||
if in_block_comment || in_attribute_bracket {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -214,108 +232,269 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti
|
||||
None
|
||||
}
|
||||
|
||||
enum ParserState {
|
||||
Normal,
|
||||
InSingleQuote,
|
||||
InDoubleQuote,
|
||||
}
|
||||
fn split_pwsh_args(code: &str) -> Vec<&str> {
|
||||
let mut chars = code.char_indices().peekable();
|
||||
let mut state = ParserState::Normal;
|
||||
let mut splits = vec![];
|
||||
let mut last_idx = 0;
|
||||
while let Some((idx, char)) = chars.next() {
|
||||
match (&state, char) {
|
||||
(ParserState::Normal, '\'') => {
|
||||
state = ParserState::InSingleQuote;
|
||||
}
|
||||
(ParserState::Normal, '"') => {
|
||||
state = ParserState::InDoubleQuote;
|
||||
}
|
||||
(ParserState::InSingleQuote, '\'') => {
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
(ParserState::InDoubleQuote, '"') => {
|
||||
state = ParserState::Normal;
|
||||
}
|
||||
(ParserState::Normal, ',') => {
|
||||
splits.push(&code[last_idx..idx]);
|
||||
last_idx = idx + 1; // skip the comma
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if last_idx < code.len() {
|
||||
splits.push(&code[last_idx..]);
|
||||
}
|
||||
|
||||
splits
|
||||
}
|
||||
|
||||
fn parse_powershell_single_typ(typ: &str) -> Typ {
|
||||
match typ.to_lowercase().as_str() {
|
||||
"string" => Typ::Str(None),
|
||||
"int" | "long" => Typ::Int,
|
||||
"decimal" | "double" | "single" => Typ::Float,
|
||||
"datetime" => Typ::Datetime,
|
||||
"bool" => Typ::Bool,
|
||||
"bool" | "switch" => Typ::Bool,
|
||||
"pscustomobject" => Typ::Object(ObjectType::new(None, None)),
|
||||
_ => Typ::Str(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let param_wrapper = extract_powershell_param_block(code, false);
|
||||
let mut args = vec![];
|
||||
if let Some(param_wrapper) = param_wrapper {
|
||||
let params = split_pwsh_args(param_wrapper);
|
||||
for param in params {
|
||||
if let Some(cap) = RE_POWERSHELL_ARGS.captures(param) {
|
||||
let typ = cap.get(1).map(|x| x.as_str().to_string());
|
||||
let name = cap.get(2).unwrap().as_str().to_string();
|
||||
/// Single-pass PowerShell parameter parser.
|
||||
/// Parses the content of a param() block and extracts all parameter information.
|
||||
///
|
||||
/// This function processes PowerShell parameter declarations in a single pass, handling:
|
||||
/// - Parameter attributes: [Parameter(Mandatory)], [Parameter(Mandatory=$true)], [ValidateSet(...)], etc.
|
||||
/// - Type annotations: [string], [int[]], [PSCustomObject], etc.
|
||||
/// - Variable names: $Name, $Value, etc.
|
||||
/// - Default values: = 'text', = 25, = $env:VAR, etc.
|
||||
/// - Mandatory detection: Parameters with Mandatory attribute are marked as required
|
||||
fn parse_powershell_parameters(content: &str) -> anyhow::Result<Vec<Arg>> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum State {
|
||||
Normal,
|
||||
InSingleQuote,
|
||||
InDoubleQuote,
|
||||
InBracket,
|
||||
}
|
||||
|
||||
let mut parsed_typ = if let Some(typ) = typ {
|
||||
if typ.as_str().ends_with("[]") {
|
||||
Some(Typ::List(Box::new(parse_powershell_single_typ(
|
||||
typ.as_str().strip_suffix("[]").unwrap(),
|
||||
))))
|
||||
} else {
|
||||
Some(parse_powershell_single_typ(typ.as_str()))
|
||||
let mut args = Vec::new();
|
||||
let mut chars = content.char_indices().peekable();
|
||||
let mut state = State::Normal;
|
||||
let mut bracket_depth: i32 = 0;
|
||||
let mut paren_depth: i32 = 0;
|
||||
|
||||
// Current parameter being built
|
||||
let mut type_annotation: Option<String> = None;
|
||||
let mut var_name: Option<String> = None;
|
||||
let mut default_value: Option<String> = None;
|
||||
let mut is_mandatory = false;
|
||||
|
||||
// Track position for extracting text
|
||||
let mut last_bracket_start = None;
|
||||
let mut found_dollar = false;
|
||||
|
||||
while let Some((idx, ch)) = chars.next() {
|
||||
match state {
|
||||
State::InSingleQuote => {
|
||||
if ch == '\'' {
|
||||
state = State::Normal;
|
||||
}
|
||||
}
|
||||
State::InDoubleQuote => {
|
||||
if ch == '"' {
|
||||
// Check for escape character
|
||||
if idx > 0 && content.chars().nth(idx - 1) != Some('`') {
|
||||
state = State::Normal;
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
State::InBracket => {
|
||||
match ch {
|
||||
'[' => bracket_depth += 1,
|
||||
']' => {
|
||||
bracket_depth -= 1;
|
||||
if bracket_depth == 0 {
|
||||
// Extract the bracket content
|
||||
if let Some(start) = last_bracket_start {
|
||||
let bracket_content = &content[start + 1..idx];
|
||||
|
||||
let default = if let Some(x) = cap.get(3) {
|
||||
Some(json!(x.as_str().to_string()))
|
||||
} else if let Some(x) = cap.get(4) {
|
||||
if parsed_typ.is_none() {
|
||||
if x.as_str().parse::<i64>().is_ok() {
|
||||
parsed_typ = Some(Typ::Int);
|
||||
} else if x.as_str().parse::<f64>().is_ok() {
|
||||
parsed_typ = Some(Typ::Float);
|
||||
// Check if this is a Parameter attribute with Mandatory (case-insensitive)
|
||||
let lower = bracket_content.to_lowercase();
|
||||
if lower.starts_with("parameter(") || lower.starts_with("parameter ") {
|
||||
// Check for Mandatory (case-insensitive)
|
||||
if lower.contains("mandatory") {
|
||||
// Check if it's explicitly set to false
|
||||
if !lower.contains("mandatory=$false") && !lower.contains("mandatory = $false") {
|
||||
is_mandatory = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this looks like a type (simple word, possibly with [])
|
||||
let is_type = !bracket_content.contains('(')
|
||||
&& !bracket_content.contains('=')
|
||||
&& (bracket_content.chars().next().unwrap_or(' ').is_alphabetic()
|
||||
|| bracket_content.starts_with('['));
|
||||
|
||||
if is_type && !found_dollar {
|
||||
type_annotation = Some(bracket_content.to_string());
|
||||
}
|
||||
}
|
||||
state = State::Normal;
|
||||
last_bracket_start = None;
|
||||
}
|
||||
}
|
||||
serde_json::Number::from_str(x.as_str())
|
||||
.ok()
|
||||
.map(serde_json::Value::Number)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
'(' => paren_depth += 1,
|
||||
')' => paren_depth = paren_depth.saturating_sub(1),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
State::Normal => {
|
||||
match ch {
|
||||
'\'' => state = State::InSingleQuote,
|
||||
'"' => state = State::InDoubleQuote,
|
||||
'[' => {
|
||||
state = State::InBracket;
|
||||
bracket_depth = 1;
|
||||
last_bracket_start = Some(idx);
|
||||
}
|
||||
'$' => {
|
||||
found_dollar = true;
|
||||
// Extract variable name
|
||||
let name_start = idx + 1;
|
||||
let mut name_end = name_start;
|
||||
while let Some(&(_, next_ch)) = chars.peek() {
|
||||
if next_ch.is_alphanumeric() || next_ch == '_' {
|
||||
name_end += 1;
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var_name = Some(content[name_start..name_end].to_string());
|
||||
}
|
||||
'=' if found_dollar => {
|
||||
// Extract default value
|
||||
// Skip whitespace after =
|
||||
while let Some(&(_, next_ch)) = chars.peek() {
|
||||
if next_ch.is_whitespace() {
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
args.push(Arg {
|
||||
name: name,
|
||||
typ: parsed_typ.unwrap_or(Typ::Str(None)),
|
||||
default: default.clone(),
|
||||
otyp: None,
|
||||
has_default: default.is_some(),
|
||||
oidx: None,
|
||||
});
|
||||
let default_start = chars.peek().map(|(i, _)| *i).unwrap_or(content.len());
|
||||
let mut default_end = default_start;
|
||||
let mut in_string = false;
|
||||
let mut string_char = ' ';
|
||||
|
||||
while let Some((i, ch)) = chars.peek().copied() {
|
||||
if in_string {
|
||||
if ch == string_char && content.chars().nth(i.saturating_sub(1)) != Some('`') {
|
||||
in_string = false;
|
||||
default_end = i + 1;
|
||||
chars.next();
|
||||
} else {
|
||||
default_end = i + 1;
|
||||
chars.next();
|
||||
}
|
||||
} else if ch == '\'' || ch == '"' {
|
||||
in_string = true;
|
||||
string_char = ch;
|
||||
default_end = i + 1;
|
||||
chars.next();
|
||||
} else if ch == ',' {
|
||||
break;
|
||||
} else if ch.is_whitespace() && chars.clone().skip(1).next().map(|(_, c)| c) == Some(',') {
|
||||
break;
|
||||
} else {
|
||||
default_end = i + 1;
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
default_value = Some(content[default_start..default_end].trim().to_string());
|
||||
}
|
||||
',' => {
|
||||
// End of parameter, finalize it
|
||||
if let Some(name) = var_name.take() {
|
||||
args.push(finalize_parameter(name, type_annotation.take(), default_value.take(), is_mandatory)?);
|
||||
}
|
||||
|
||||
// Reset for next parameter
|
||||
type_annotation = None;
|
||||
var_name = None;
|
||||
default_value = None;
|
||||
is_mandatory = false;
|
||||
found_dollar = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(args))
|
||||
|
||||
// Finalize last parameter
|
||||
if let Some(name) = var_name {
|
||||
args.push(finalize_parameter(name, type_annotation, default_value, is_mandatory)?);
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn finalize_parameter(
|
||||
name: String,
|
||||
type_annotation: Option<String>,
|
||||
default_value: Option<String>,
|
||||
is_mandatory: bool,
|
||||
) -> anyhow::Result<Arg> {
|
||||
// Store the original PowerShell type for use in the executor
|
||||
let otyp = type_annotation.clone();
|
||||
|
||||
let mut parsed_typ = if let Some(typ) = type_annotation {
|
||||
if typ.ends_with("[]") {
|
||||
Some(Typ::List(Box::new(parse_powershell_single_typ(
|
||||
typ.strip_suffix("[]").unwrap(),
|
||||
))))
|
||||
} else {
|
||||
Some(parse_powershell_single_typ(&typ))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let default = if let Some(default_str) = default_value {
|
||||
// Try to parse as string (quoted)
|
||||
if (default_str.starts_with('"') && default_str.ends_with('"'))
|
||||
|| (default_str.starts_with('\'') && default_str.ends_with('\''))
|
||||
{
|
||||
Some(json!(default_str[1..default_str.len() - 1].to_string()))
|
||||
} else {
|
||||
// Try to parse as number
|
||||
if parsed_typ.is_none() {
|
||||
if default_str.parse::<i64>().is_ok() {
|
||||
parsed_typ = Some(Typ::Int);
|
||||
} else if default_str.parse::<f64>().is_ok() {
|
||||
parsed_typ = Some(Typ::Float);
|
||||
}
|
||||
}
|
||||
serde_json::Number::from_str(&default_str)
|
||||
.ok()
|
||||
.map(serde_json::Value::Number)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// has_default semantics:
|
||||
// - true: parameter is optional (has a default value OR is not mandatory)
|
||||
// - false: parameter is required (marked as Mandatory AND no default value)
|
||||
// Simplified: A parameter is optional unless it's mandatory without a default
|
||||
let has_default = default.is_some() || !is_mandatory;
|
||||
|
||||
Ok(Arg {
|
||||
name,
|
||||
typ: parsed_typ.unwrap_or(Typ::Str(None)),
|
||||
default: default.clone(),
|
||||
otyp,
|
||||
has_default,
|
||||
oidx: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_powershell_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
|
||||
let param_wrapper = extract_powershell_param_block(code, false);
|
||||
if let Some(param_wrapper) = param_wrapper {
|
||||
Ok(Some(parse_powershell_parameters(param_wrapper)?))
|
||||
} else {
|
||||
Ok(Some(vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -402,23 +581,23 @@ non_required="${5:-}"
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: None, // No type annotation
|
||||
name: "Msg".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: Some("string".to_string()), // [string]
|
||||
name: "Msg2".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: None, // No type annotation
|
||||
name: "Dflt".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: Some(json!("default value, with comma")),
|
||||
@@ -426,7 +605,7 @@ non_required="${5:-}"
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: Some("int".to_string()), // [int]
|
||||
name: "Nb".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(3)),
|
||||
@@ -434,7 +613,7 @@ non_required="${5:-}"
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: None, // Type inferred from default value
|
||||
name: "Nb2".to_string(),
|
||||
typ: Typ::Float,
|
||||
default: Some(json!(5.0)),
|
||||
@@ -442,7 +621,7 @@ non_required="${5:-}"
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: None, // Type inferred from default value
|
||||
name: "Nb3".to_string(),
|
||||
typ: Typ::Int,
|
||||
default: Some(json!(5)),
|
||||
@@ -450,35 +629,35 @@ non_required="${5:-}"
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: None, // No type annotation
|
||||
name: "Wahoo".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: Some("PSCustomObject".to_string()), // [PSCustomObject]
|
||||
name: "Obj".to_string(),
|
||||
typ: Typ::Object(ObjectType::new(None, None)),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: Some("string[]".to_string()), // [string[]]
|
||||
name: "Arr".to_string(),
|
||||
typ: Typ::List(Box::new(Typ::Str(None))),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: true, // Optional (not mandatory)
|
||||
oidx: None
|
||||
},
|
||||
Arg {
|
||||
otyp: None,
|
||||
otyp: Some("string".to_string()), // [string] (last type bracket with Mandatory)
|
||||
name: "Message".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
has_default: false, // Required (Mandatory attribute)
|
||||
oidx: None
|
||||
}
|
||||
],
|
||||
@@ -602,6 +781,242 @@ non_required="${5:-}"
|
||||
extract_powershell_param_block("function test-x{ param($Name)\n}", false),
|
||||
None
|
||||
);
|
||||
|
||||
// Valid: [CmdletBinding()] before param
|
||||
assert_eq!(
|
||||
extract_powershell_param_block("[CmdletBinding()]\nparam($Name)", false),
|
||||
Some("$Name")
|
||||
);
|
||||
assert_eq!(
|
||||
extract_powershell_param_block("[CmdletBinding()]\nparam($Name, $Age)", true),
|
||||
Some("param($Name, $Age)")
|
||||
);
|
||||
|
||||
// Valid: [CmdletBinding()] with options before param
|
||||
assert_eq!(
|
||||
extract_powershell_param_block(
|
||||
"[CmdletBinding(SupportsShouldProcess=$true)]\nparam($Path)",
|
||||
false
|
||||
),
|
||||
Some("$Path")
|
||||
);
|
||||
|
||||
// Valid: Multiple attributes before param
|
||||
assert_eq!(
|
||||
extract_powershell_param_block(
|
||||
"[CmdletBinding()]\n[OutputType([string])]\nparam($Value)",
|
||||
false
|
||||
),
|
||||
Some("$Value")
|
||||
);
|
||||
|
||||
// Valid: CmdletBinding with comments
|
||||
assert_eq!(
|
||||
extract_powershell_param_block(
|
||||
"# My function\n[CmdletBinding()]\nparam($Name)",
|
||||
false
|
||||
),
|
||||
Some("$Name")
|
||||
);
|
||||
|
||||
// Valid: CmdletBinding with whitespace variations
|
||||
assert_eq!(
|
||||
extract_powershell_param_block(
|
||||
"[CmdletBinding()] \n param($Name)",
|
||||
false
|
||||
),
|
||||
Some("$Name")
|
||||
);
|
||||
|
||||
// Invalid: Unclosed attribute bracket
|
||||
assert_eq!(
|
||||
extract_powershell_param_block("[CmdletBinding(\nparam($Name)", false),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_powershell_sig_with_parameter_attributes() -> anyhow::Result<()> {
|
||||
// Test with [Parameter(Mandatory=$true)] attribute
|
||||
let code = r#"[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Name,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]$Age = 25
|
||||
)"#;
|
||||
let result = parse_powershell_sig(code)?;
|
||||
assert_eq!(result.args.len(), 2);
|
||||
assert_eq!(result.args[0].name, "Name");
|
||||
assert_eq!(result.args[0].typ, Typ::Str(None));
|
||||
assert_eq!(result.args[0].has_default, false);
|
||||
assert_eq!(result.args[1].name, "Age");
|
||||
assert_eq!(result.args[1].typ, Typ::Int);
|
||||
assert_eq!(result.args[1].has_default, true);
|
||||
assert_eq!(result.args[1].default, Some(json!(25)));
|
||||
|
||||
// Test with complex attributes
|
||||
let code2 = r#"param(
|
||||
[Parameter(Mandatory=$true, Position=0)]
|
||||
[ValidateSet('Red', 'Green', 'Blue')]
|
||||
[string]$Color,
|
||||
[Parameter(ValueFromPipeline=$true)]
|
||||
[string[]]$Items
|
||||
)"#;
|
||||
let result2 = parse_powershell_sig(code2)?;
|
||||
assert_eq!(result2.args.len(), 2);
|
||||
assert_eq!(result2.args[0].name, "Color");
|
||||
assert_eq!(result2.args[0].typ, Typ::Str(None));
|
||||
assert_eq!(result2.args[1].name, "Items");
|
||||
assert_eq!(result2.args[1].typ, Typ::List(Box::new(Typ::Str(None))));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_powershell_single_pass_parser() -> anyhow::Result<()> {
|
||||
// Test the single-pass parser with a complex real-world example
|
||||
let code = r#"[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true, Position=0, HelpMessage="Enter the server name")]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$ServerName,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateRange(1, 65535)]
|
||||
[int]$Port = 8080,
|
||||
|
||||
[Parameter(ValueFromPipeline=$true)]
|
||||
[string[]]$LogFiles,
|
||||
|
||||
[ValidateSet('Debug', 'Info', 'Warning', 'Error')]
|
||||
[string]$LogLevel = 'Info',
|
||||
|
||||
[PSCustomObject]$Config
|
||||
)"#;
|
||||
let result = parse_powershell_sig(code)?;
|
||||
|
||||
assert_eq!(result.args.len(), 5);
|
||||
|
||||
// ServerName: mandatory string with no default
|
||||
assert_eq!(result.args[0].name, "ServerName");
|
||||
assert_eq!(result.args[0].typ, Typ::Str(None));
|
||||
assert_eq!(result.args[0].has_default, false);
|
||||
|
||||
// Port: optional int with default
|
||||
assert_eq!(result.args[1].name, "Port");
|
||||
assert_eq!(result.args[1].typ, Typ::Int);
|
||||
assert_eq!(result.args[1].default, Some(json!(8080)));
|
||||
assert_eq!(result.args[1].has_default, true);
|
||||
|
||||
// LogFiles: string array (no mandatory, so optional)
|
||||
assert_eq!(result.args[2].name, "LogFiles");
|
||||
assert_eq!(result.args[2].typ, Typ::List(Box::new(Typ::Str(None))));
|
||||
assert_eq!(result.args[2].has_default, true); // Optional (not mandatory)
|
||||
|
||||
// LogLevel: string with default (ValidateSet is ignored but doesn't break parsing)
|
||||
assert_eq!(result.args[3].name, "LogLevel");
|
||||
assert_eq!(result.args[3].typ, Typ::Str(None));
|
||||
assert_eq!(result.args[3].default, Some(json!("Info")));
|
||||
assert_eq!(result.args[3].has_default, true);
|
||||
|
||||
// Config: PSCustomObject (no mandatory, so optional)
|
||||
assert_eq!(result.args[4].name, "Config");
|
||||
assert_eq!(result.args[4].typ, Typ::Object(ObjectType::new(None, None)));
|
||||
assert_eq!(result.args[4].has_default, true); // Optional (not mandatory)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_powershell_mandatory_attribute() -> anyhow::Result<()> {
|
||||
// Test various forms of the Mandatory attribute
|
||||
let code = r#"param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RequiredNoEquals,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$RequiredWithTrue,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$RequiredWithSpaces,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$NotRequired,
|
||||
|
||||
[Parameter(Position=0)]
|
||||
[string]$NoMandatory,
|
||||
|
||||
[string]$PlainRequired = "default",
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$RequiredInt
|
||||
)"#;
|
||||
let result = parse_powershell_sig(code)?;
|
||||
|
||||
assert_eq!(result.args.len(), 7);
|
||||
|
||||
// RequiredNoEquals: mandatory without =$true
|
||||
assert_eq!(result.args[0].name, "RequiredNoEquals");
|
||||
assert_eq!(result.args[0].has_default, false); // Required (mandatory, no default)
|
||||
|
||||
// RequiredWithTrue: mandatory with =$true
|
||||
assert_eq!(result.args[1].name, "RequiredWithTrue");
|
||||
assert_eq!(result.args[1].has_default, false); // Required
|
||||
|
||||
// RequiredWithSpaces: mandatory with spaces
|
||||
assert_eq!(result.args[2].name, "RequiredWithSpaces");
|
||||
assert_eq!(result.args[2].has_default, false); // Required
|
||||
|
||||
// NotRequired: explicitly Mandatory=$false
|
||||
assert_eq!(result.args[3].name, "NotRequired");
|
||||
assert_eq!(result.args[3].has_default, true); // Optional (not mandatory)
|
||||
|
||||
// NoMandatory: no Mandatory attribute
|
||||
assert_eq!(result.args[4].name, "NoMandatory");
|
||||
assert_eq!(result.args[4].has_default, true); // Optional (not mandatory)
|
||||
|
||||
// PlainRequired: has default value (always optional)
|
||||
assert_eq!(result.args[5].name, "PlainRequired");
|
||||
assert_eq!(result.args[5].has_default, true); // Optional (has default)
|
||||
assert_eq!(result.args[5].default, Some(json!("default")));
|
||||
|
||||
// RequiredInt: mandatory int
|
||||
assert_eq!(result.args[6].name, "RequiredInt");
|
||||
assert_eq!(result.args[6].typ, Typ::Int);
|
||||
assert_eq!(result.args[6].has_default, false); // Required
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_powershell_case_insensitive_parameter() -> anyhow::Result<()> {
|
||||
// Test that [parameter(...)] is case-insensitive
|
||||
let code = r#"param(
|
||||
[parameter(Mandatory)]
|
||||
[string]$LowerCase,
|
||||
|
||||
[PARAMETER(MANDATORY=$TRUE)]
|
||||
[string]$UpperCase,
|
||||
|
||||
[Parameter(mandatory=$true)]
|
||||
[string]$MixedCase
|
||||
)"#;
|
||||
let result = parse_powershell_sig(code)?;
|
||||
|
||||
assert_eq!(result.args.len(), 3);
|
||||
|
||||
// All should be detected as mandatory
|
||||
assert_eq!(result.args[0].name, "LowerCase");
|
||||
assert_eq!(result.args[0].has_default, false);
|
||||
|
||||
assert_eq!(result.args[1].name, "UpperCase");
|
||||
assert_eq!(result.args[1].has_default, false);
|
||||
|
||||
assert_eq!(result.args[2].name, "MixedCase");
|
||||
assert_eq!(result.args[2].has_default, false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn set_license_key(_license_key: String) -> () {
|
||||
pub async fn set_license_key(_license_key: String, _db: Option<&windmill_common::db::DB>) -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
|
||||
|
||||
+46
-48
@@ -79,7 +79,7 @@ use windmill_common::{
|
||||
OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
|
||||
};
|
||||
use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING};
|
||||
use windmill_queue::{SameWorkerPayload, cancel_job, get_queued_job_v2};
|
||||
use windmill_queue::{cancel_job, get_queued_job_v2, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
|
||||
@@ -2219,7 +2219,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
timeouts
|
||||
};
|
||||
|
||||
@@ -2253,7 +2252,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let timeouts = non_restartable_jobs
|
||||
.into_iter()
|
||||
.map(|x| (x, ErrorMessage::RestartDisabled))
|
||||
@@ -2289,59 +2287,59 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
continue;
|
||||
}
|
||||
if let Some(job) = job.unwrap() {
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
{
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&job.permissioned_as_email,
|
||||
&job.id,
|
||||
None,
|
||||
Some(format!("handle_zombie_jobs")),
|
||||
)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
{
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&job.permissioned_as_email,
|
||||
&job.id,
|
||||
None,
|
||||
Some(format!("handle_zombie_jobs")),
|
||||
)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
|
||||
let client = AuthedClient::new(
|
||||
base_internal_url.to_string(),
|
||||
job.workspace_id.to_string(),
|
||||
token,
|
||||
None,
|
||||
);
|
||||
let client = AuthedClient::new(
|
||||
base_internal_url.to_string(),
|
||||
job.workspace_id.to_string(),
|
||||
token,
|
||||
None,
|
||||
);
|
||||
|
||||
let error_message = format!(
|
||||
let error_message = format!(
|
||||
"Job timed out after no ping from job since {} (ZOMBIE_JOB_TIMEOUT: {}, reason: {:?}).\nThis likely means that the job died on worker {}, OOM are a common reason for worker crashes.\nCheck the workers around the time of the last ping and the exit code if any.",
|
||||
job.last_ping.unwrap_or_default(),
|
||||
*ZOMBIE_JOB_TIMEOUT,
|
||||
error_kind.to_string(),
|
||||
job.worker.clone().unwrap_or_default(),
|
||||
);
|
||||
let memory_peak = job.memory_peak.unwrap_or(0);
|
||||
let _ = handle_job_error(
|
||||
db,
|
||||
&client,
|
||||
&windmill_queue::MiniCompletedJob::from(job),
|
||||
memory_peak,
|
||||
None,
|
||||
error::Error::ExecutionErr(error_message),
|
||||
true,
|
||||
Some(&same_worker_tx_never_used),
|
||||
"",
|
||||
node_name,
|
||||
send_result_never_used,
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut windmill_common::bench::BenchmarkIter::new(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let memory_peak = job.memory_peak.unwrap_or(0);
|
||||
let _ = handle_job_error(
|
||||
db,
|
||||
&client,
|
||||
&windmill_queue::MiniCompletedJob::from(job),
|
||||
memory_peak,
|
||||
None,
|
||||
error::Error::ExecutionErr(error_message),
|
||||
true,
|
||||
Some(&same_worker_tx_never_used),
|
||||
"",
|
||||
node_name,
|
||||
send_result_never_used,
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut windmill_common::bench::BenchmarkIter::new(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2769,7 +2769,7 @@ async fn test_result_format(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let response = windmill_api::jobs::run_wait_result(
|
||||
&db,
|
||||
Uuid::parse_str(ordered_result_job_id).unwrap(),
|
||||
"test-workspace".to_string(),
|
||||
"test-workspace",
|
||||
None,
|
||||
"test-user",
|
||||
)
|
||||
|
||||
@@ -94,6 +94,7 @@ tempfile.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio-tar.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
postgres-native-tls.workspace = true
|
||||
hmac.workspace = true
|
||||
cookie.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.575.4
|
||||
version: 1.576.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -5956,6 +5956,80 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_wait_result/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version and wait until completion
|
||||
operationId: runWaitResultFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
get:
|
||||
summary: run flow by version with GET and wait until completion
|
||||
operationId: runWaitResultFlowByVersionGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and stream updates via SSE
|
||||
@@ -6032,6 +6106,94 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version and stream updates via SSE
|
||||
operationId: runAndStreamFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run flow by version with GET and stream updates via SSE
|
||||
operationId: runAndStreamFlowByVersionGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/p/{path}:
|
||||
post:
|
||||
summary: run script by path and stream updates via SSE
|
||||
@@ -7680,6 +7842,64 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
/w/{workspace}/jobs/run/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version
|
||||
operationId: runFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- name: scheduled_for
|
||||
description: when to schedule this job (leave empty for immediate run)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
responses:
|
||||
"201":
|
||||
description: job created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
/w/{workspace}/jobs/run/batch_rerun_jobs:
|
||||
post:
|
||||
summary: re-run multiple jobs
|
||||
@@ -18835,6 +19055,8 @@ components:
|
||||
properties:
|
||||
lock_error_logs:
|
||||
type: string
|
||||
version_id:
|
||||
type: number
|
||||
|
||||
ExtraPerms:
|
||||
type: object
|
||||
|
||||
@@ -13,7 +13,10 @@ use anyhow::anyhow;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
use {std::sync::Arc, tokio::sync::RwLock};
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> {
|
||||
pub async fn validate_license_key(
|
||||
_license_key: String,
|
||||
_db: Option<&crate::db::DB>,
|
||||
) -> anyhow::Result<(String, bool)> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
|
||||
@@ -1135,6 +1135,7 @@ async fn get_flow_by_path(
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
@@ -1174,8 +1175,9 @@ async fn get_flow_by_path(
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.created_by AS edited_by,
|
||||
NULL AS starred
|
||||
|
||||
+1045
-1020
File diff suppressed because it is too large
Load Diff
@@ -71,10 +71,12 @@ fn is_public_route_whitelisted(path: &str) -> bool {
|
||||
"/user/login_callback/*",
|
||||
"/api/workspaces/users",
|
||||
"/api/users/whoami",
|
||||
"/api/apps_u/*",
|
||||
"/api/oauth/list_connects",
|
||||
"/api/oauth/list_logins",
|
||||
"/public/*",
|
||||
"/a/*",
|
||||
"/api/oauth/get_connect/*",
|
||||
"/Inter-Variable.woff2",
|
||||
];
|
||||
|
||||
|
||||
@@ -701,23 +701,49 @@ async fn setup_ducklake_catalog_db_inner(
|
||||
sslmode = ssl_mode
|
||||
);
|
||||
|
||||
let (client, connection) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
tokio_postgres::connect(&conn_str, tokio_postgres::NoTls),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("timeout: {}", e.to_string())))?
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("error: {}", e.to_string())))?;
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
let (client, join_handle) = if ssl_mode == "require" {
|
||||
use native_tls::TlsConnector;
|
||||
use postgres_native_tls::MakeTlsConnector;
|
||||
|
||||
let mut connector = TlsConnector::builder();
|
||||
connector.danger_accept_invalid_certs(true);
|
||||
connector.danger_accept_invalid_hostnames(true);
|
||||
|
||||
let (client, connection) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
tokio_postgres::connect(
|
||||
&conn_str,
|
||||
MakeTlsConnector::new(connector.build().map_err(to_anyhow)?),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("timeout: {}", e.to_string())))?
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("error: {}", e.to_string())))?;
|
||||
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
(client, join_handle)
|
||||
} else {
|
||||
let (client, connection) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
tokio_postgres::connect(&conn_str, tokio_postgres::NoTls),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("timeout: {}", e.to_string())))?
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("error: {}", e.to_string())))?;
|
||||
|
||||
let join_handle = tokio::spawn(async move { connection.await });
|
||||
(client, join_handle)
|
||||
};
|
||||
|
||||
logs.db_connect = "OK".to_string();
|
||||
|
||||
client
|
||||
.batch_execute(&format!(
|
||||
"GRANT CONNECT ON DATABASE \"{dbname}\" TO ducklake_user;
|
||||
GRANT USAGE ON SCHEMA public TO ducklake_user;
|
||||
GRANT CREATE ON SCHEMA public TO ducklake_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ducklake_user;"
|
||||
GRANT USAGE ON SCHEMA public TO ducklake_user;
|
||||
GRANT CREATE ON SCHEMA public TO ducklake_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ducklake_user;"
|
||||
))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
||||
@@ -9,9 +9,19 @@ use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
FlowVersionInfo, db::{UserDB, UserDbWithAuthed}, error::Result, flows::{FlowModuleValue, Retry}, get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, jobs::{JobPayload, get_has_preprocessor_from_content_and_lang, script_path_to_payload}, scripts::{ScriptHash, ScriptLang, get_full_hub_script_by_path}, triggers::{
|
||||
HubOrWorkspaceId, RUNNABLE_FORMAT_VERSION_CACHE, RunnableFormat, RunnableFormatVersion, TriggerInfo, TriggerKind
|
||||
}, users::username_to_permissioned_as, utils::StripPath, worker::to_raw_value
|
||||
db::{UserDB, UserDbWithAuthed},
|
||||
error::Result,
|
||||
flows::{FlowModuleValue, Retry},
|
||||
get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path,
|
||||
jobs::{get_has_preprocessor_from_content_and_lang, script_path_to_payload, JobPayload},
|
||||
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
|
||||
triggers::{
|
||||
HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerInfo, TriggerKind,
|
||||
RUNNABLE_FORMAT_VERSION_CACHE,
|
||||
},
|
||||
users::username_to_permissioned_as,
|
||||
utils::StripPath,
|
||||
worker::to_raw_value,
|
||||
};
|
||||
use windmill_queue::{push, PushArgs, PushArgsOwned, PushIsolationLevel};
|
||||
|
||||
@@ -48,7 +58,7 @@ struct PartialSchema {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum RunnableId {
|
||||
FlowPath(String),
|
||||
FlowId(FlowId),
|
||||
ScriptId(ScriptId),
|
||||
HubScript(String),
|
||||
}
|
||||
@@ -67,7 +77,33 @@ impl RunnableId {
|
||||
}
|
||||
|
||||
pub fn from_flow_path(path: &str) -> Self {
|
||||
Self::FlowPath(path.to_string())
|
||||
Self::FlowId(FlowId::FlowPath(path.to_string()))
|
||||
}
|
||||
|
||||
pub fn from_flow_version(version: i64) -> Self {
|
||||
Self::FlowId(FlowId::FlowVersion(version))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum FlowId {
|
||||
FlowPath(String),
|
||||
FlowVersion(i64),
|
||||
}
|
||||
|
||||
impl FlowId {
|
||||
async fn get_flow_version_id(self, workspace_id: &str, db: &DB) -> Result<i64> {
|
||||
let version_id = match self {
|
||||
FlowId::FlowPath(path) => {
|
||||
let info =
|
||||
get_latest_flow_version_info_for_path(None, db, workspace_id, &path, true)
|
||||
.await?;
|
||||
info.version
|
||||
}
|
||||
FlowId::FlowVersion(version) => version,
|
||||
};
|
||||
|
||||
Ok(version_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,9 +283,8 @@ pub async fn get_runnable_format(
|
||||
},
|
||||
)
|
||||
}
|
||||
RunnableId::FlowPath(path) => {
|
||||
let FlowVersionInfo { version, .. } =
|
||||
get_latest_flow_version_info_for_path(None, &db, workspace_id, &path, true).await?;
|
||||
RunnableId::FlowId(flow_id) => {
|
||||
let version = flow_id.get_flow_version_id(workspace_id, db).await?;
|
||||
|
||||
let key = (
|
||||
HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()),
|
||||
@@ -260,7 +295,7 @@ pub async fn get_runnable_format(
|
||||
let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key);
|
||||
|
||||
if let Some(runnable_format) = runnable_format {
|
||||
tracing::debug!("Using cached runnable format for flow {path}");
|
||||
tracing::debug!("Using cached runnable format for flow version {version}");
|
||||
return Ok(runnable_format);
|
||||
}
|
||||
|
||||
@@ -271,11 +306,9 @@ pub async fn get_runnable_format(
|
||||
schema as \"schema: _\"
|
||||
FROM flow_version
|
||||
WHERE
|
||||
path = $1
|
||||
AND workspace_id = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1",
|
||||
path,
|
||||
id = $1
|
||||
AND workspace_id = $2",
|
||||
version,
|
||||
workspace_id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
@@ -612,8 +645,7 @@ pub async fn trigger_runnable_and_wait_for_result(
|
||||
)
|
||||
.await?;
|
||||
let (result, success) =
|
||||
run_wait_result_internal(db, uuid, workspace_id.to_string(), early_return, &username)
|
||||
.await?;
|
||||
run_wait_result_internal(db, uuid, &workspace_id, early_return, &username).await?;
|
||||
|
||||
if delete_after_use.unwrap_or(false) {
|
||||
delete_job_metadata_after_use(&db, uuid).await?;
|
||||
@@ -657,7 +689,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result(
|
||||
.await?;
|
||||
|
||||
let (result, success) =
|
||||
run_wait_result_internal(db, uuid, workspace_id.to_string(), early_return, &username)
|
||||
run_wait_result_internal(db, uuid, &workspace_id, early_return, &username)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
|
||||
@@ -66,6 +66,7 @@ pub struct FlowWithStarred {
|
||||
pub starred: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lock_error_logs: Option<String>,
|
||||
pub version_id: i64,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
|
||||
@@ -769,7 +769,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_latest_flow_version_info_for_path_from_version<
|
||||
pub fn get_flow_version_info_from_version<
|
||||
'a,
|
||||
'e,
|
||||
A: sqlx::Acquire<'e, Database = Postgres> + Send + 'a,
|
||||
@@ -782,7 +782,6 @@ pub fn get_latest_flow_version_info_for_path_from_version<
|
||||
async move {
|
||||
// as instructed in the docstring of sqlx::Acquire
|
||||
let key = (w_id.to_string(), version);
|
||||
|
||||
match FLOW_INFO_CACHE.get(&key) {
|
||||
Some(info) => {
|
||||
tracing::debug!("Using cached flow version info for {version} ({path})");
|
||||
@@ -791,21 +790,37 @@ pub fn get_latest_flow_version_info_for_path_from_version<
|
||||
_ => {
|
||||
tracing::debug!("Fetching flow version info for {version} ({path})");
|
||||
let mut conn = db.acquire().await?;
|
||||
let info = sqlx::query_as!(
|
||||
FlowVersionInfo,
|
||||
"SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version
|
||||
FROM flow
|
||||
INNER JOIN flow_version
|
||||
ON flow_version.id = $3
|
||||
WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
path,
|
||||
w_id,
|
||||
version
|
||||
)
|
||||
.fetch_optional(&mut *conn)
|
||||
.await?;
|
||||
let flow_info =
|
||||
sqlx::query_as!(
|
||||
FlowVersionInfo,
|
||||
r#"
|
||||
SELECT
|
||||
flow_version.id AS version,
|
||||
flow_version.value->>'early_return' as early_return,
|
||||
flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,
|
||||
(flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,
|
||||
flow.tag,
|
||||
flow.dedicated_worker,
|
||||
flow.on_behalf_of_email,
|
||||
flow.edited_by
|
||||
FROM
|
||||
flow_version
|
||||
INNER JOIN flow
|
||||
ON flow.path = flow_version.path AND
|
||||
flow.workspace_id = flow_version.workspace_id
|
||||
WHERE
|
||||
flow_version.workspace_id = $1 AND
|
||||
flow_version.path = $2 AND
|
||||
flow_version.id = $3
|
||||
"#,
|
||||
w_id,
|
||||
path,
|
||||
version,
|
||||
)
|
||||
.fetch_optional(&mut *conn)
|
||||
.await?;
|
||||
|
||||
let info = utils::not_found_if_none(info, "flow", path)?;
|
||||
let info = utils::not_found_if_none(flow_info, "flow", path)?;
|
||||
|
||||
FLOW_INFO_CACHE.insert(key, info.clone());
|
||||
|
||||
@@ -825,7 +840,7 @@ pub async fn get_latest_flow_version_info_for_path<'e>(
|
||||
// as instructed in the docstring of sqlx::Acquire
|
||||
let version =
|
||||
get_latest_flow_version_id_for_path(db_authed, &db.clone(), w_id, path, use_cache).await?;
|
||||
get_latest_flow_version_info_for_path_from_version(db, version, w_id, path).await
|
||||
get_flow_version_info_from_version(db, version, w_id, path).await
|
||||
}
|
||||
|
||||
async fn get_latest_flow_version_for_path<'e, E: sqlx::PgExecutor<'e>>(
|
||||
|
||||
@@ -17,8 +17,8 @@ use std::str::FromStr;
|
||||
use windmill_common::db::Authed;
|
||||
use windmill_common::ee_oss::LICENSE_KEY_VALID;
|
||||
use windmill_common::flows::Retry;
|
||||
use windmill_common::get_flow_version_info_from_version;
|
||||
use windmill_common::get_latest_flow_version_id_for_path;
|
||||
use windmill_common::get_latest_flow_version_info_for_path_from_version;
|
||||
use windmill_common::jobs::check_tag_available_for_workspace_internal;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::jobs::JobTriggerKind;
|
||||
@@ -65,7 +65,7 @@ async fn get_schedule_metadata<'c>(
|
||||
.await?;
|
||||
|
||||
let FlowVersionInfo { tag, on_behalf_of_email, edited_by, .. } =
|
||||
get_latest_flow_version_info_for_path_from_version(
|
||||
get_flow_version_info_from_version(
|
||||
&mut **tx,
|
||||
version,
|
||||
&schedule.workspace_id,
|
||||
@@ -293,16 +293,13 @@ pub async fn push_scheduled_job<'c>(
|
||||
|
||||
let FlowVersionInfo {
|
||||
version, tag, dedicated_worker, on_behalf_of_email, edited_by, ..
|
||||
} = get_latest_flow_version_info_for_path_from_version(
|
||||
} = get_flow_version_info_from_version(
|
||||
&mut *tx,
|
||||
version,
|
||||
&schedule.workspace_id,
|
||||
&schedule.script_path,
|
||||
)
|
||||
.warn_after_seconds_with_sql(
|
||||
1,
|
||||
"get_latest_flow_version_info_for_path_from_version".to_string(),
|
||||
)
|
||||
.warn_after_seconds_with_sql(1, "get_flow_version_info_from_version".to_string())
|
||||
.await?;
|
||||
|
||||
(
|
||||
|
||||
@@ -255,20 +255,42 @@ pub async fn handle_powershell_job(
|
||||
job.args.as_ref()
|
||||
};
|
||||
|
||||
let args_owned = windmill_parser_bash::parse_powershell_sig(&content)?
|
||||
let parsed_sig = windmill_parser_bash::parse_powershell_sig(&content)?;
|
||||
|
||||
parsed_sig
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
(
|
||||
arg.name.clone(),
|
||||
job_args.and_then(|x| x.get(&arg.name).map(|x| raw_to_pwsh_param(x.get()))),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(String, Option<String>)>>();
|
||||
.filter_map(|arg| {
|
||||
let value_opt = job_args.and_then(|x| x.get(&arg.name));
|
||||
|
||||
args_owned
|
||||
.into_iter()
|
||||
.filter_map(|(n, v)| v.map(|v| format!("-{n} {v}")))
|
||||
// Check if this is a switch parameter (only [switch], not [bool])
|
||||
let is_switch = arg.otyp.as_ref().map(|t| {
|
||||
t.to_lowercase() == "switch"
|
||||
}).unwrap_or(false);
|
||||
|
||||
if is_switch {
|
||||
// Handle switch parameters: -SwitchName or omit
|
||||
if let Some(value) = value_opt {
|
||||
match serde_json::from_str::<serde_json::Value>(value.get()) {
|
||||
Ok(serde_json::Value::Bool(true)) => {
|
||||
// Switch is enabled: just pass -SwitchName
|
||||
Some(format!("-{}", arg.name))
|
||||
}
|
||||
Ok(serde_json::Value::Bool(false)) | _ => {
|
||||
// Switch is disabled or invalid: omit the parameter
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No value provided, omit the switch (defaults to false)
|
||||
None
|
||||
}
|
||||
} else {
|
||||
// Regular parameter (including [bool]): format as -ParamName Value
|
||||
// For [bool] parameters, this will be -ParamName $true or -ParamName $false
|
||||
value_opt.map(|v| format!("-{} {}", arg.name, raw_to_pwsh_param(v.get())))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
};
|
||||
|
||||
+1
-1
@@ -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.575.4";
|
||||
export const VERSION = "v1.576.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.575.4";
|
||||
export const VERSION = "1.576.0";
|
||||
|
||||
export const WM_FORK_PREFIX = "wm-fork";
|
||||
|
||||
|
||||
+8
-8
@@ -1,14 +1,14 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function parse_assets_sql(code: string): string;
|
||||
export function parse_db_resource(code: string): string | undefined;
|
||||
export function parse_bash(code: string): string;
|
||||
export function parse_mssql(code: string): string;
|
||||
export function parse_oracledb(code: string): string;
|
||||
export function parse_powershell(code: string): string;
|
||||
export function parse_bigquery(code: string): string;
|
||||
export function parse_oracledb(code: string): string;
|
||||
export function parse_graphql(code: string): string;
|
||||
export function parse_snowflake(code: string): string;
|
||||
export function parse_duckdb(code: string): string;
|
||||
export function parse_sql(code: string): string;
|
||||
export function parse_assets_sql(code: string): string;
|
||||
export function parse_mysql(code: string): string;
|
||||
export function parse_bash(code: string): string;
|
||||
export function parse_duckdb(code: string): string;
|
||||
export function parse_db_resource(code: string): string | undefined;
|
||||
export function parse_bigquery(code: string): string;
|
||||
export function parse_snowflake(code: string): string;
|
||||
export function parse_sql(code: string): string;
|
||||
|
||||
@@ -68,60 +68,6 @@ function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return decodeText(ptr, len);
|
||||
}
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_assets_sql(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_assets_sql(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export function parse_db_resource(code) {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_db_resource(ptr0, len0);
|
||||
let v2;
|
||||
if (ret[0] !== 0) {
|
||||
v2 = getStringFromWasm0(ret[0], ret[1]).slice();
|
||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||
}
|
||||
return v2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_bash(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_bash(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
@@ -141,25 +87,6 @@ export function parse_mssql(code) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_oracledb(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_oracledb(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
@@ -183,13 +110,13 @@ export function parse_powershell(code) {
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_bigquery(code) {
|
||||
export function parse_oracledb(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_bigquery(ptr0, len0);
|
||||
const ret = wasm.parse_oracledb(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
@@ -221,13 +148,51 @@ export function parse_graphql(code) {
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_snowflake(code) {
|
||||
export function parse_assets_sql(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_snowflake(ptr0, len0);
|
||||
const ret = wasm.parse_assets_sql(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_mysql(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_mysql(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_bash(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_bash(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
@@ -255,17 +220,33 @@ export function parse_duckdb(code) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export function parse_db_resource(code) {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_db_resource(ptr0, len0);
|
||||
let v2;
|
||||
if (ret[0] !== 0) {
|
||||
v2 = getStringFromWasm0(ret[0], ret[1]).slice();
|
||||
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
||||
}
|
||||
return v2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_sql(code) {
|
||||
export function parse_bigquery(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_sql(ptr0, len0);
|
||||
const ret = wasm.parse_bigquery(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
@@ -278,13 +259,32 @@ export function parse_sql(code) {
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_mysql(code) {
|
||||
export function parse_snowflake(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_mysql(ptr0, len0);
|
||||
const ret = wasm.parse_snowflake(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_sql(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.parse_sql(ptr0, len0);
|
||||
deferred2_0 = ret[0];
|
||||
deferred2_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
|
||||
Binary file not shown.
Generated
+6
-6
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.575.4",
|
||||
"version": "1.576.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.575.4",
|
||||
"version": "1.576.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -79,7 +79,7 @@
|
||||
"windmill-parser-wasm-nu": "1.510.1",
|
||||
"windmill-parser-wasm-php": "1.574.1",
|
||||
"windmill-parser-wasm-py": "1.538.0",
|
||||
"windmill-parser-wasm-regex": "1.574.1",
|
||||
"windmill-parser-wasm-regex": "1.575.4",
|
||||
"windmill-parser-wasm-ruby": "1.526.1",
|
||||
"windmill-parser-wasm-rust": "1.558.1",
|
||||
"windmill-parser-wasm-ts": "1.565.0",
|
||||
@@ -13759,9 +13759,9 @@
|
||||
"integrity": "sha512-s+bdIgT/fA5em3zYUwF8D14uA/dZh7iu0krZYZQqZUO7txN37hwSCVfovbMkIwm4zPbsJ50mU8DRLt7UpAPZIw=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-regex": {
|
||||
"version": "1.574.1",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.574.1.tgz",
|
||||
"integrity": "sha512-KnNBnpTGcnBPSzTQHdsjTPJbRQ81iem98eUnMGO8Zt1qgBFJ/eFZPwmUg6TZMbMITzTi0QqRCJj08eQID1FcPg=="
|
||||
"version": "1.575.4",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.575.4.tgz",
|
||||
"integrity": "sha512-G7Ni8XxdSKLNew50B1KRmwn/z9qq3obpEDaaDFS7Z74MeUdaBB/TD69SY6RxDL8SZrh0Tl7SCbS/GGLnt1AzkQ=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-ruby": {
|
||||
"version": "1.526.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.575.4",
|
||||
"version": "1.576.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -144,7 +144,7 @@
|
||||
"windmill-parser-wasm-nu": "1.510.1",
|
||||
"windmill-parser-wasm-php": "1.574.1",
|
||||
"windmill-parser-wasm-py": "1.538.0",
|
||||
"windmill-parser-wasm-regex": "1.574.1",
|
||||
"windmill-parser-wasm-regex": "1.575.4",
|
||||
"windmill-parser-wasm-ruby": "1.526.1",
|
||||
"windmill-parser-wasm-rust": "1.558.1",
|
||||
"windmill-parser-wasm-ts": "1.565.0",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
import { usePromise } from '$lib/svelte5Utils.svelte'
|
||||
import { pollJobResult } from './jobs/utils'
|
||||
import { sameTopDomainOrigin } from '$lib/cookies'
|
||||
|
||||
interface Props {
|
||||
step?: number
|
||||
@@ -239,7 +240,7 @@
|
||||
function popupListener(event) {
|
||||
console.log('Received oauth popup message', event)
|
||||
let data = event.data
|
||||
if (event.origin == null || event.origin !== window.location.origin) {
|
||||
if (!sameTopDomainOrigin(event.origin, window.location.origin)) {
|
||||
console.log(
|
||||
'Received oauth popup message from different origin',
|
||||
event.origin,
|
||||
@@ -397,6 +398,7 @@
|
||||
}
|
||||
window.addEventListener('message', popupListener)
|
||||
window.addEventListener('storage', handleStorageEvent)
|
||||
console.log('opening popup', url.toString())
|
||||
window.open(url.toString(), '_blank', 'popup=true')
|
||||
step += 1
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<Button variant="default" on:click={appConnect?.back ?? (() => {})}>Back</Button>
|
||||
{/if}
|
||||
|
||||
<Button {disabled} on:click={appConnect?.next ?? (() => {})}>
|
||||
<Button variant="accent" {disabled} on:click={appConnect?.next ?? (() => {})}>
|
||||
{#if step == 2 && !manual}
|
||||
Connect
|
||||
{:else if step == 1}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { sameTopDomainOrigin } from '$lib/cookies'
|
||||
|
||||
interface Props {
|
||||
rd?: string | undefined
|
||||
@@ -212,8 +213,9 @@
|
||||
|
||||
function popupListener(event) {
|
||||
let data = event.data
|
||||
console.log('popupListener', data, event.origin, window.location.origin)
|
||||
if (event.origin !== window.location.origin) {
|
||||
// console.log('popupListener', data, event.origin, window.location.origin)
|
||||
if (!sameTopDomainOrigin(event.origin, window.location.origin)) {
|
||||
console.log('popupListener from different origin', event.origin, window.location.origin)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -261,7 +263,9 @@
|
||||
console.error('Could not persist redirection to local storage', e)
|
||||
}
|
||||
}
|
||||
let url = base + '/api/oauth/login/' + provider
|
||||
let url = base + '/api/oauth/login/' + provider + (popup ? '?close=true' : '')
|
||||
console.log('storeRedirect', popup, url)
|
||||
|
||||
if (popup) {
|
||||
localStorage.setItem('closeUponLogin', 'true')
|
||||
window.addEventListener('message', popupListener)
|
||||
|
||||
@@ -1668,7 +1668,7 @@
|
||||
args={hasPreprocessor && selectedInputTab !== 'preprocessor' ? {} : args}
|
||||
isDeployed={savedScript && !savedScript?.draft_only}
|
||||
schema={script.schema}
|
||||
hash={script.parent_hash}
|
||||
runnableVersion={script.parent_hash}
|
||||
onDeployTrigger={handleDeployTrigger}
|
||||
/>
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<script lang="ts">
|
||||
import { User, UserRoundX } from 'lucide-svelte'
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { base } from '$app/paths'
|
||||
import { page } from '$app/state'
|
||||
import Login from '$lib/components/Login.svelte'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { Alert, Skeleton } from '$lib/components/common'
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { onMount, setContext } from 'svelte'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '../types'
|
||||
import { UserService, type AppWithLastVersion, type GlobalWhoamiResponse } from '$lib/gen'
|
||||
import { urlParamsToObject } from '$lib/utils'
|
||||
import { goto } from '$app/navigation'
|
||||
import AppPreview from './AppPreview.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
let {
|
||||
notExists,
|
||||
noPermission,
|
||||
jwtError,
|
||||
onLoginSuccess,
|
||||
app,
|
||||
workspace
|
||||
}: {
|
||||
notExists: boolean
|
||||
noPermission: boolean
|
||||
jwtError: boolean
|
||||
onLoginSuccess: () => void
|
||||
app: (AppWithLastVersion & { value: any }) | undefined
|
||||
workspace: string | undefined
|
||||
} = $props()
|
||||
|
||||
setContext(IS_APP_PUBLIC_CONTEXT_KEY, true)
|
||||
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
|
||||
const darkMode =
|
||||
window.localStorage.getItem('dark-mode') ??
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
|
||||
if (darkMode === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
|
||||
let globalUser = $state<GlobalWhoamiResponse | undefined>(undefined)
|
||||
async function loadGlobalUser() {
|
||||
try {
|
||||
globalUser = await UserService.globalWhoami()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
// const user = await fetch('/api/global/user')
|
||||
// console.log(user)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// this is to avoid loading global user if the userStore is set at loading
|
||||
setTimeout(() => {
|
||||
if ($userStore) return
|
||||
loadGlobalUser()
|
||||
}, 2000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="z-50 text-xs fixed bottom-1 right-2 {$enterpriseLicense && !isCloudHosted()
|
||||
? 'transition-opacity delay-1000 duration-1000 opacity-20 hover:delay-0 hover:opacity-100'
|
||||
: ''}"
|
||||
>
|
||||
<a href="https://windmill.dev" class="whitespace-nowrap text-primary inline-flex items-center"
|
||||
>Powered by <WindmillIcon /> Windmill</a
|
||||
>
|
||||
</div>
|
||||
|
||||
{#snippet userInfo(child)}
|
||||
<div class="flex gap-1 items-center"><User size={14} />{child}</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="z-50 text-2xs text-primary absolute top-3 left-2"
|
||||
>{#if $userStore}
|
||||
{@render userInfo($userStore.username)}
|
||||
{:else if globalUser}
|
||||
{@render userInfo(globalUser.email)}
|
||||
{:else}<UserRoundX size={14} />{/if}
|
||||
</div>
|
||||
|
||||
{#if notExists}
|
||||
<div class="px-4 mt-20"
|
||||
><Alert type="error" title="Not found"
|
||||
>There was an error loading the app, is the url correct? <a href={base}>Go to Windmill</a>
|
||||
</Alert></div
|
||||
>
|
||||
{:else if noPermission}
|
||||
<div class="px-4 mt-20 w-full text-center font-bold text-xl"> This app requires read access </div>
|
||||
<div class="text-center mt-8 text-sm text-primary">
|
||||
{#if $userStore}You are logged in but have no read access to this app{:else if globalUser && workspace}
|
||||
You are logged in but are not a member of the workspace <span class="text-xl font-bold"
|
||||
>{workspace}</span
|
||||
> this app is part of
|
||||
{:else}You must be logged in and have read access to this app{/if}</div
|
||||
>
|
||||
<div class="px-2 mx-auto mt-20 max-w-xl w-full">
|
||||
{#if !jwtError}
|
||||
<Login {onLoginSuccess} popup rd={page.url.toString()} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if app}
|
||||
{#key app}
|
||||
<div
|
||||
class={twMerge(
|
||||
'min-h-screen h-full w-full flex',
|
||||
app?.value?.['css']?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={app?.value?.['css']?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
noBackend={false}
|
||||
context={{
|
||||
email: $userStore?.email,
|
||||
name: $userStore?.name,
|
||||
groups: $userStore?.groups,
|
||||
username: $userStore?.username,
|
||||
query: urlParamsToObject(page.url.searchParams),
|
||||
hash: page.url.hash.substring(1)
|
||||
}}
|
||||
{workspace}
|
||||
summary={app.summary}
|
||||
app={app.value}
|
||||
appPath={app.path}
|
||||
{breakpoint}
|
||||
policy={app.policy}
|
||||
isEditor={false}
|
||||
replaceStateFn={(path) => goto(path)}
|
||||
gotoFn={(path, opt) => goto(path, opt)}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
<Skeleton layout={[[4], 0.5, [50]]} />
|
||||
{/if}
|
||||
@@ -41,7 +41,7 @@
|
||||
newItem?: boolean
|
||||
currentPath: string
|
||||
fakeInitialPath?: string
|
||||
hash?: string | undefined
|
||||
runnableVersion?: string | undefined
|
||||
args?: Record<string, any>
|
||||
initialPath: string
|
||||
isFlow: boolean
|
||||
@@ -59,7 +59,7 @@
|
||||
newItem = false,
|
||||
currentPath,
|
||||
fakeInitialPath = '',
|
||||
hash = undefined,
|
||||
runnableVersion = undefined,
|
||||
args = {},
|
||||
initialPath,
|
||||
isFlow,
|
||||
@@ -363,7 +363,7 @@
|
||||
{initialPath}
|
||||
{fakeInitialPath}
|
||||
{currentPath}
|
||||
{hash}
|
||||
{runnableVersion}
|
||||
{isDeployed}
|
||||
small={useVerticalTriggerBar}
|
||||
{args}
|
||||
@@ -416,7 +416,7 @@
|
||||
{hasPreprocessor}
|
||||
{canHavePreprocessor}
|
||||
args={config}
|
||||
data={{ args, hash, emailDomain }}
|
||||
data={{ args, hash: !isFlow ? runnableVersion : undefined, emailDomain }}
|
||||
{isValid}
|
||||
triggerDeployed={!triggersState.selectedTrigger.isDraft}
|
||||
on:applyArgs
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
initialPath: string
|
||||
fakeInitialPath: string
|
||||
currentPath: string
|
||||
hash?: string
|
||||
runnableVersion?: string
|
||||
isDeployed: boolean
|
||||
small: boolean
|
||||
args: Record<string, any>
|
||||
@@ -45,7 +45,7 @@
|
||||
initialPath,
|
||||
fakeInitialPath,
|
||||
currentPath,
|
||||
hash,
|
||||
runnableVersion,
|
||||
small,
|
||||
args,
|
||||
newItem,
|
||||
@@ -68,7 +68,7 @@
|
||||
<WebhooksPanel
|
||||
{isFlow}
|
||||
path={initialPath || fakeInitialPath}
|
||||
{hash}
|
||||
{runnableVersion}
|
||||
token=""
|
||||
{args}
|
||||
scopes={isFlow ? [`jobs:run:flows:${currentPath}`] : [`jobs:run:scripts:${currentPath}`]}
|
||||
@@ -80,7 +80,7 @@
|
||||
scopes={isFlow ? [`jobs:run:flows:${currentPath}`] : [`jobs:run:scripts:${currentPath}`]}
|
||||
path={initialPath || fakeInitialPath}
|
||||
{isFlow}
|
||||
{hash}
|
||||
runnableVersion={!isFlow ? runnableVersion : undefined}
|
||||
{onEmailDomain}
|
||||
/>
|
||||
{:else if selectedTrigger.type === 'schedule'}
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
import UserSettings from '$lib/components/UserSettings.svelte'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
|
||||
let requestType: 'hash' | 'path' = $state('path')
|
||||
let requestType: 'runnableVersion' | 'path' = $state('path')
|
||||
|
||||
function emailAddress() {
|
||||
const pathOrHash = requestType === 'hash' ? hash : path.replaceAll('/', '.')
|
||||
const pathOrHash = requestType === 'runnableVersion' ? runnableVersion : path.replaceAll('/', '.')
|
||||
const plainPrefix = `${$workspaceStore}+${
|
||||
(requestType === 'hash' ? 'hash.' : isFlow ? 'flow.' : '') + pathOrHash
|
||||
(requestType === 'runnableVersion' ? 'hash.' : isFlow ? 'flow.' : '') + pathOrHash
|
||||
}+${token}`
|
||||
const encodedPrefix = base32
|
||||
.stringify(new TextEncoder().encode(plainPrefix), {
|
||||
@@ -32,7 +32,7 @@
|
||||
interface Props {
|
||||
token?: string
|
||||
isFlow?: boolean
|
||||
hash?: string | undefined
|
||||
runnableVersion?: string | undefined
|
||||
path: string
|
||||
userSettings: UserSettings
|
||||
emailDomain?: string | null
|
||||
@@ -42,7 +42,7 @@
|
||||
let {
|
||||
token = $bindable(''),
|
||||
isFlow = false,
|
||||
hash = undefined,
|
||||
runnableVersion = undefined,
|
||||
path,
|
||||
userSettings,
|
||||
emailDomain = null,
|
||||
@@ -90,7 +90,7 @@
|
||||
<ToggleButtonGroup class="w-auto" bind:selected={requestType}>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton label="By path" value="path" {item} />
|
||||
<ToggleButton label="By hash" value="hash" {item} />
|
||||
<ToggleButton label="By hash" value="runnableVersion" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
token: string
|
||||
scopes?: string[]
|
||||
isFlow?: boolean
|
||||
hash?: string | undefined
|
||||
runnableVersion?: string | undefined
|
||||
path: string
|
||||
onEmailDomain: (domain: string) => void
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
token = $bindable(),
|
||||
scopes = [],
|
||||
isFlow = false,
|
||||
hash = undefined,
|
||||
runnableVersion = undefined,
|
||||
path,
|
||||
onEmailDomain
|
||||
}: Props = $props()
|
||||
@@ -70,7 +70,7 @@
|
||||
<Skeleton layout={[[18]]} />
|
||||
{:else}
|
||||
{#if emailDomain}
|
||||
<DefaultEmailConfigSection {hash} {token} {path} {isFlow} {userSettings} {emailDomain} />
|
||||
<DefaultEmailConfigSection {runnableVersion} {token} {path} {isFlow} {userSettings} {emailDomain} />
|
||||
{:else}
|
||||
<div>
|
||||
<Alert title="Email triggers are disabled" size="xs" type="warning">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
interface Props {
|
||||
isFlow?: boolean
|
||||
path?: string
|
||||
hash?: string | undefined
|
||||
runnableVersion?: string | undefined
|
||||
token?: string
|
||||
runnableArgs: any
|
||||
triggerTokens?: TriggerTokens | undefined
|
||||
@@ -36,46 +36,38 @@
|
||||
let {
|
||||
isFlow = false,
|
||||
path = '',
|
||||
hash = undefined,
|
||||
runnableVersion = undefined,
|
||||
token = $bindable(''),
|
||||
runnableArgs,
|
||||
triggerTokens = $bindable(undefined),
|
||||
scopes = []
|
||||
}: Props = $props()
|
||||
|
||||
let webhooks: {
|
||||
async: {
|
||||
get: {}
|
||||
post: {
|
||||
hash?: string
|
||||
path: string
|
||||
}
|
||||
const WEBHOOK_BASE_URL = `${location.origin}${base}/api/w/${$workspaceStore}/jobs`
|
||||
|
||||
let baseWebhookUrl = $derived.by(() => {
|
||||
let webhookUrlPath: string
|
||||
|
||||
if (isFlow) {
|
||||
webhookUrlPath = runnableId == 'path' ? `f/${path}` : `fv/${runnableVersion}`
|
||||
} else {
|
||||
webhookUrlPath = runnableId == 'path' ? `p/${path}` : `h/${runnableVersion}`
|
||||
}
|
||||
sync: {
|
||||
get: {
|
||||
path: string
|
||||
}
|
||||
post: {
|
||||
hash?: string
|
||||
path: string
|
||||
}
|
||||
|
||||
if (requestType == 'async') {
|
||||
return `${WEBHOOK_BASE_URL}/run/${webhookUrlPath}`
|
||||
} else if (requestType == 'sync') {
|
||||
return `${WEBHOOK_BASE_URL}/run_wait_result/${webhookUrlPath}`
|
||||
} else {
|
||||
return `${WEBHOOK_BASE_URL}/run_and_stream/${webhookUrlPath}`
|
||||
}
|
||||
sync_sse: {
|
||||
get: {
|
||||
hash?: string
|
||||
path: string
|
||||
}
|
||||
post: {
|
||||
hash?: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
} = $derived(isFlow ? computeFlowWebhooks(path) : computeScriptWebhooks(hash, path))
|
||||
})
|
||||
|
||||
let selectedTab: string = $state('rest')
|
||||
let userSettings: UserSettings | undefined = $state()
|
||||
let requestType = $state(DEFAULT_WEBHOOK_TYPE) as 'async' | 'sync' | 'sync_sse'
|
||||
let callMethod = $state('post') as 'get' | 'post'
|
||||
let runnableId = $state('path') as 'hash' | 'path'
|
||||
let runnableId = $state('path') as 'runnableVersion' | 'path'
|
||||
let tokenType = $state('headers') as 'query' | 'headers'
|
||||
|
||||
$effect(() => {
|
||||
@@ -91,7 +83,7 @@
|
||||
: runnableArgs
|
||||
})
|
||||
let url: string = $derived(
|
||||
webhooks[requestType][callMethod][runnableId] +
|
||||
baseWebhookUrl +
|
||||
(tokenType === 'query'
|
||||
? `?token=${token}${
|
||||
callMethod === 'get' || requestType === 'sync_sse'
|
||||
@@ -105,70 +97,6 @@
|
||||
}`)
|
||||
)
|
||||
|
||||
function computeScriptWebhooks(hash: string | undefined, path: string) {
|
||||
let webhookBase = `${location.origin}${base}/api/w/${$workspaceStore}/jobs`
|
||||
return {
|
||||
async: {
|
||||
get: {},
|
||||
post: {
|
||||
hash: `${webhookBase}/run/h/${hash}`,
|
||||
path: `${webhookBase}/run/p/${path}`
|
||||
}
|
||||
},
|
||||
sync: {
|
||||
get: {
|
||||
path: `${webhookBase}/run_wait_result/p/${path}`
|
||||
},
|
||||
post: {
|
||||
hash: `${webhookBase}/run_wait_result/h/${hash}`,
|
||||
path: `${webhookBase}/run_wait_result/p/${path}`
|
||||
}
|
||||
},
|
||||
sync_sse: {
|
||||
get: {
|
||||
path: `${webhookBase}/run_and_stream/p/${path}`,
|
||||
hash: `${webhookBase}/run_and_stream/h/${hash}`
|
||||
},
|
||||
post: {
|
||||
hash: `${webhookBase}/run_and_stream/h/${hash}`,
|
||||
path: `${webhookBase}/run_and_stream/p/${path}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function computeFlowWebhooks(path: string) {
|
||||
let webhooksBase = `${location.origin}${base}/api/w/${$workspaceStore}/jobs`
|
||||
|
||||
let urlAsync = `${webhooksBase}/run/f/${path}`
|
||||
let urlSync = `${webhooksBase}/run_wait_result/f/${path}`
|
||||
let urlStream = `${webhooksBase}/run_and_stream/f/${path}`
|
||||
return {
|
||||
async: {
|
||||
get: {},
|
||||
post: {
|
||||
path: urlAsync
|
||||
}
|
||||
},
|
||||
sync: {
|
||||
get: {
|
||||
path: urlSync
|
||||
},
|
||||
post: {
|
||||
path: urlSync
|
||||
}
|
||||
},
|
||||
sync_sse: {
|
||||
get: {
|
||||
path: urlStream
|
||||
},
|
||||
post: {
|
||||
path: urlStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function headers() {
|
||||
const headers = {}
|
||||
if (callMethod === 'post') {
|
||||
@@ -433,16 +361,19 @@ done`
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</Label>
|
||||
{#if !isFlow}
|
||||
<Label label="Reference type">
|
||||
<ToggleButtonGroup bind:selected={runnableId}>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton label="Path" value="path" {item} />
|
||||
<ToggleButton label="Hash" value="hash" disabled={!hash} {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</Label>
|
||||
{/if}
|
||||
<Label label="Reference type">
|
||||
<ToggleButtonGroup bind:selected={runnableId}>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton label="Path" value="path" {item} />
|
||||
<ToggleButton
|
||||
label={isFlow ? 'Flow version' : 'Hash'}
|
||||
value="runnableVersion"
|
||||
disabled={!runnableVersion}
|
||||
{item}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</Label>
|
||||
<Label label="Token configuration">
|
||||
<ToggleButtonGroup bind:selected={tokenType}>
|
||||
{#snippet children({ item })}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
args?: Record<string, any>
|
||||
scopes?: string[]
|
||||
isFlow?: boolean
|
||||
hash?: string | undefined
|
||||
runnableVersion?: string | undefined
|
||||
path: string
|
||||
newItem?: boolean
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
args = {},
|
||||
scopes = [],
|
||||
isFlow = false,
|
||||
hash = undefined,
|
||||
runnableVersion = undefined,
|
||||
path,
|
||||
newItem = false
|
||||
}: Props = $props()
|
||||
@@ -39,5 +39,5 @@
|
||||
{isFlow ? 'flow' : 'script'}.
|
||||
</Alert>
|
||||
{/if}
|
||||
<WebhooksConfigSection {isFlow} {path} {hash} {token} runnableArgs={args} {scopes} />
|
||||
<WebhooksConfigSection {isFlow} {path} {runnableVersion} {token} runnableArgs={args} {scopes} />
|
||||
</Section>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Reads the value of a cookie by name.
|
||||
* @param {string} name - The name of the cookie to retrieve.
|
||||
* @returns {string | undefined} The cookie value, or undefined if not found.
|
||||
*/
|
||||
export function getCookie(name: string): string | undefined {
|
||||
const match = document.cookie.match(
|
||||
new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)')
|
||||
)
|
||||
return match ? decodeURIComponent(match[1]) : undefined
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this only check the last 2 segments to work for popup on shared top-domain
|
||||
export function sameTopDomainOrigin(origin: string | null, desktopOrigin: string): boolean {
|
||||
if (origin == null) {
|
||||
return false
|
||||
}
|
||||
const getLastTwoSegments = (url: string) => {
|
||||
const parts = url.split('.');
|
||||
return parts.length >= 2 ? parts.slice(-2).join('.') : url;
|
||||
};
|
||||
if (origin.includes('.') && desktopOrigin.includes('.')) {
|
||||
return getLastTwoSegments(origin) === getLastTwoSegments(desktopOrigin);
|
||||
} else {
|
||||
return origin === desktopOrigin;
|
||||
}
|
||||
}
|
||||
@@ -675,6 +675,7 @@
|
||||
{#if flow}
|
||||
<TriggersEditor
|
||||
{args}
|
||||
runnableVersion={flow.version_id?.toString()}
|
||||
initialPath={flow.path}
|
||||
currentPath={flow.path}
|
||||
noEditor={true}
|
||||
|
||||
@@ -791,7 +791,7 @@
|
||||
{#if script}
|
||||
<TriggersEditor
|
||||
{args}
|
||||
hash={script.hash}
|
||||
runnableVersion={script.hash}
|
||||
initialPath={script.path}
|
||||
currentPath={script.path}
|
||||
noEditor={true}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { BROWSER } from 'esm-env'
|
||||
import { base } from '$lib/base'
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '$lib/components/apps/types'
|
||||
|
||||
import { Alert, Skeleton } from '$lib/components/common'
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { AppService, OpenAPI, type AppWithLastVersion } from '$lib/gen'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { setLicense } from '$lib/enterpriseUtils'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import Login from '$lib/components/Login.svelte'
|
||||
|
||||
import { getUserExt } from '$lib/user'
|
||||
import { User, UserRoundX } from 'lucide-svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { page } from '$app/state'
|
||||
import { urlParamsToObject } from '$lib/utils'
|
||||
import PublicApp from '$lib/components/apps/editor/PublicApp.svelte'
|
||||
|
||||
let app: (AppWithLastVersion & { value: any }) | undefined = $state(undefined)
|
||||
let notExists = $state(false)
|
||||
let noPermission = $state(false)
|
||||
|
||||
let jwtError = $state(false)
|
||||
setContext(IS_APP_PUBLIC_CONTEXT_KEY, true)
|
||||
|
||||
function isJwt(t: string) {
|
||||
// simply check that the first part is a valid base64 encoded json
|
||||
@@ -56,9 +44,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
const parsedCustomPath = parseCustomPath(page.params.path ?? '')
|
||||
|
||||
let workspace: string | undefined = $state(undefined)
|
||||
async function loadApp() {
|
||||
const parsedCustomPath = parseCustomPath(page.params.path ?? '')
|
||||
|
||||
if (parsedCustomPath.jwt) {
|
||||
const token = 'jwt_ext_' + parsedCustomPath.jwt
|
||||
OpenAPI.TOKEN = token
|
||||
@@ -69,6 +58,7 @@
|
||||
app = await AppService.getPublicAppByCustomPath({
|
||||
customPath: parsedCustomPath.path
|
||||
})
|
||||
workspace = app.workspace_id
|
||||
workspaceStore.set(app.workspace_id)
|
||||
noPermission = false
|
||||
notExists = false
|
||||
@@ -95,93 +85,15 @@
|
||||
setLicense()
|
||||
loadApp()
|
||||
}
|
||||
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
|
||||
const darkMode =
|
||||
window.localStorage.getItem('dark-mode') ??
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
|
||||
if (darkMode === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="z-50 text-xs fixed bottom-1 right-2 {$enterpriseLicense && !isCloudHosted()
|
||||
? 'transition-opacity delay-1000 duration-1000 opacity-20 hover:delay-0 hover:opacity-100'
|
||||
: ''}"
|
||||
>
|
||||
<a href="https://windmill.dev" class="whitespace-nowrap text-primary inline-flex items-center"
|
||||
>Powered by <WindmillIcon /> Windmill</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="z-50 text-2xs text-primary absolute top-3 left-2"
|
||||
>{#if $userStore}
|
||||
<div class="flex gap-1 items-center"><User size={14} />{$userStore.username}</div>
|
||||
{:else}<UserRoundX size={14} />{/if}
|
||||
</div>
|
||||
|
||||
{#if notExists}
|
||||
<div class="px-4 mt-20"
|
||||
><Alert type="error" title="Not found"
|
||||
>There was an error loading the app, is the url correct? <a href={base}>Go to Windmill</a>
|
||||
</Alert></div
|
||||
>
|
||||
{:else if noPermission}
|
||||
<div class="px-4 mt-20 w-full text-center font-bold text-xl"
|
||||
>{#if $userStore}You are logged in but have no read access to this app{:else}You must be logged
|
||||
in and have read access to this app{/if}</div
|
||||
>
|
||||
<div class="px-2 mx-auto mt-20 max-w-xl w-full">
|
||||
{#if !jwtError}
|
||||
<Login
|
||||
onLoginSuccess={() => {
|
||||
|
||||
// window.location.reload()
|
||||
loadApp()
|
||||
app = app
|
||||
}}
|
||||
popup
|
||||
rd={page.url.toString()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if app}
|
||||
{#key app}
|
||||
<div
|
||||
class={twMerge(
|
||||
'min-h-screen h-full w-full flex',
|
||||
app?.value?.['css']?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={app?.value?.['css']?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
noBackend={false}
|
||||
context={{
|
||||
email: $userStore?.email,
|
||||
name: $userStore?.name,
|
||||
groups: $userStore?.groups,
|
||||
username: $userStore?.username,
|
||||
query: urlParamsToObject(page.url.searchParams),
|
||||
hash: page.url.hash.substring(1)
|
||||
}}
|
||||
workspace={page.params.workspace}
|
||||
summary={app.summary}
|
||||
app={app.value}
|
||||
appPath={app.path}
|
||||
{breakpoint}
|
||||
policy={app.policy}
|
||||
isEditor={false}
|
||||
replaceStateFn={(path) => goto(path)}
|
||||
gotoFn={(path, opt) => goto(path, opt)}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
<Skeleton layout={[[4], 0.5, [50]]} />
|
||||
{/if}
|
||||
<PublicApp
|
||||
{workspace}
|
||||
{notExists}
|
||||
{noPermission}
|
||||
{jwtError}
|
||||
{app}
|
||||
onLoginSuccess={() => {
|
||||
loadApp()
|
||||
}}
|
||||
></PublicApp>
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { BROWSER } from 'esm-env'
|
||||
import { base } from '$lib/base'
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '$lib/components/apps/types'
|
||||
|
||||
import { WindmillIcon } from '$lib/components/icons'
|
||||
import { AppService, OpenAPI, type AppWithLastVersion } from '$lib/gen'
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { setLicense } from '$lib/enterpriseUtils'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import Login from '$lib/components/Login.svelte'
|
||||
|
||||
import { getUserExt } from '$lib/user'
|
||||
import { User, UserRoundX } from 'lucide-svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
|
||||
import { page } from '$app/state'
|
||||
import { urlParamsToObject } from '$lib/utils'
|
||||
import PublicApp from '$lib/components/apps/editor/PublicApp.svelte'
|
||||
|
||||
let app: (AppWithLastVersion & { value: any }) | undefined = $state(undefined)
|
||||
let notExists = $state(false)
|
||||
let noPermission = $state(false)
|
||||
|
||||
let jwtError = $state(false)
|
||||
setContext(IS_APP_PUBLIC_CONTEXT_KEY, true)
|
||||
|
||||
function parseSecret(secret: string): { secret: string; jwt: string } {
|
||||
const parts = secret.split('/')
|
||||
@@ -59,6 +47,11 @@
|
||||
|
||||
if (BROWSER) {
|
||||
setLicense()
|
||||
loadAll()
|
||||
}
|
||||
|
||||
function loadAll() {
|
||||
console.log('loadAll')
|
||||
loadUser().then(() => {
|
||||
loadApp()
|
||||
})
|
||||
@@ -81,95 +74,15 @@
|
||||
console.warn('Anonymous user')
|
||||
}
|
||||
}
|
||||
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
|
||||
const darkMode =
|
||||
window.localStorage.getItem('dark-mode') ??
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
|
||||
if (darkMode === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="z-50 text-xs fixed bottom-1 right-2 {$enterpriseLicense && !isCloudHosted()
|
||||
? 'transition-opacity delay-1000 duration-1000 opacity-20 hover:delay-0 hover:opacity-100'
|
||||
: ''}"
|
||||
>
|
||||
<a href="https://windmill.dev" class="whitespace-nowrap text-primary inline-flex items-center"
|
||||
>Powered by <WindmillIcon /> Windmill</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="z-50 text-2xs text-primary absolute top-3 left-2"
|
||||
>{#if $userStore}
|
||||
<div class="flex gap-1 items-center"><User size={14} />{$userStore.username}</div>
|
||||
{:else}<UserRoundX size={14} />{/if}
|
||||
</div>
|
||||
|
||||
{#if notExists}
|
||||
<div class="px-4 mt-20"
|
||||
><Alert type="error" title="Not found"
|
||||
>There was an error loading the app, is the url correct? <a href={base}>Go to Windmill</a>
|
||||
</Alert></div
|
||||
>
|
||||
{:else if noPermission}
|
||||
<div class="px-4 mt-20 w-full text-center font-bold text-xl"
|
||||
>{#if $userStore}You are logged in but have no read access to this app{:else}You must be logged
|
||||
in and have read access to this app{/if}</div
|
||||
>
|
||||
<div class="px-2 mx-auto mt-20 max-w-xl w-full">
|
||||
{#if !jwtError}
|
||||
<Login
|
||||
onLoginSuccess={() => {
|
||||
console.log('login')
|
||||
// window.location.reload()
|
||||
loadUser().then(() => {
|
||||
loadApp()
|
||||
})
|
||||
app = app
|
||||
}}
|
||||
popup
|
||||
rd={page.url.toString()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if app}
|
||||
{#key app}
|
||||
<div
|
||||
class={twMerge(
|
||||
'min-h-screen h-full w-full flex',
|
||||
app?.value?.['css']?.['app']?.['viewer']?.class,
|
||||
'wm-app-viewer'
|
||||
)}
|
||||
style={app?.value?.['css']?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
noBackend={false}
|
||||
context={{
|
||||
email: $userStore?.email,
|
||||
name: $userStore?.name,
|
||||
groups: $userStore?.groups,
|
||||
username: $userStore?.username,
|
||||
query: urlParamsToObject(page.url.searchParams),
|
||||
hash: page.url.hash.substring(1)
|
||||
}}
|
||||
workspace={page.params.workspace}
|
||||
summary={app.summary}
|
||||
app={app.value}
|
||||
appPath={app.path}
|
||||
{breakpoint}
|
||||
policy={app.policy}
|
||||
isEditor={false}
|
||||
replaceStateFn={(path) => goto(path)}
|
||||
gotoFn={(path, opt) => goto(path, opt)}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
<Skeleton layout={[[4], 0.5, [50]]} />
|
||||
{/if}
|
||||
<PublicApp
|
||||
{app}
|
||||
workspace={page.params.workspace}
|
||||
{notExists}
|
||||
{noPermission}
|
||||
{jwtError}
|
||||
onLoginSuccess={() => {
|
||||
loadAll()
|
||||
}}
|
||||
></PublicApp>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import { parseQueryParams } from '$lib/utils'
|
||||
import { page } from '$app/state'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { getCookie } from '$lib/cookies'
|
||||
// import { getAndDeleteCookie } from '$lib/cookies'
|
||||
|
||||
let error = page.url.searchParams.get('error')
|
||||
let clientName = page.params.client_name ?? ''
|
||||
@@ -19,11 +21,14 @@
|
||||
let state = page.url.searchParams.get('state') ?? undefined
|
||||
|
||||
onMount(async () => {
|
||||
// const closeCookie = getAndDeleteCookie('close')
|
||||
// console.log('closeCookie', closeCookie)
|
||||
const rd = localStorage.getItem('rd')
|
||||
if (rd) {
|
||||
localStorage.removeItem('rd')
|
||||
}
|
||||
const closeUponLogin = localStorage.getItem('closeUponLogin') == 'true'
|
||||
const cookieCloseUponLogin = getCookie('close') == 'true'
|
||||
const closeUponLogin = cookieCloseUponLogin ?? localStorage.getItem('closeUponLogin') == 'true'
|
||||
if (error) {
|
||||
sendUserToast(`Error trying to login with ${clientName} ${error}`, true)
|
||||
if (closeUponLogin) {
|
||||
|
||||
+10
-2
@@ -11,7 +11,15 @@ const version = JSON.parse(json)
|
||||
const config = {
|
||||
server: {
|
||||
https: process.env.HTTPS === 'true',
|
||||
allowedHosts: ['localhost', '127.0.0.1', '0.0.0.0', 'rubendev.wimill.xyz', 'windmill.xyz'],
|
||||
allowedHosts: [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'0.0.0.0',
|
||||
'rubendev.wimill.xyz',
|
||||
'windmill.xyz',
|
||||
'app.windmill.xyz',
|
||||
'public.windmill.xyz'
|
||||
],
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'^/api/w/[^/]+/s3_proxy/.*': {
|
||||
@@ -64,7 +72,7 @@ const config = {
|
||||
exclude: [
|
||||
'@codingame/monaco-vscode-standalone-typescript-language-features',
|
||||
'@codingame/monaco-vscode-standalone-languages'
|
||||
],
|
||||
]
|
||||
},
|
||||
worker: {
|
||||
format: 'es'
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.575.4"
|
||||
wmill_pg = ">=1.575.4"
|
||||
wmill = ">=1.576.0"
|
||||
wmill_pg = ">=1.576.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.575.4
|
||||
version: 1.576.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.575.4'
|
||||
ModuleVersion = '1.576.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill-pg"
|
||||
version = "1.575.4"
|
||||
version = "1.576.0"
|
||||
description = "An extension client for the wmill client library focused on pg"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.575.4",
|
||||
"version": "1.576.0",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.575.4",
|
||||
"version": "1.576.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.575.4
|
||||
1.576.0
|
||||
|
||||
Reference in New Issue
Block a user