mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 00:03:08 +00:00
Merge branch 'main' into alp/slack_interactive_approvals
This commit is contained in:
@@ -16,7 +16,7 @@ sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/
|
||||
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i '' -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i '' -e "/^ModuleVersion =/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
sed -i '' -e "/^[[:space:]]*ModuleVersion[[:space:]]*=/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
# sed -i '' -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
|
||||
sed -i '' -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
sed -i '' -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
|
||||
@@ -17,7 +17,7 @@ sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/pac
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^ModuleVersion =/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
sed -i -e "/^[[:space:]]*ModuleVersion[[:space:]]*=/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1
|
||||
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## [1.435.2](https://github.com/windmill-labs/windmill/compare/v1.435.1...v1.435.2) (2024-12-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* job search toast on error ([#4851](https://github.com/windmill-labs/windmill/issues/4851)) ([a99e63f](https://github.com/windmill-labs/windmill/commit/a99e63f5435725c42e38b46933ff3caa345002b5))
|
||||
|
||||
## [1.435.1](https://github.com/windmill-labs/windmill/compare/v1.435.0...v1.435.1) (2024-12-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve critical alerts filters ([548cfcf](https://github.com/windmill-labs/windmill/commit/548cfcfbde23ac7ae129e10f6e92d57516e61f20))
|
||||
|
||||
## [1.435.0](https://github.com/windmill-labs/windmill/compare/v1.434.2...v1.435.0) (2024-12-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* app custom paths ([#4828](https://github.com/windmill-labs/windmill/issues/4828)) ([1ec6c6f](https://github.com/windmill-labs/windmill/commit/1ec6c6f765904361e641d89495890bc87e8544aa))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass USERPROFILE on windows ([5404ec9](https://github.com/windmill-labs/windmill/commit/5404ec9b48e8d7a0cb27b2319d54f04c94ec6fd0))
|
||||
|
||||
## [1.434.2](https://github.com/windmill-labs/windmill/compare/v1.434.1...v1.434.2) (2024-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* custom http routes auth ([#4835](https://github.com/windmill-labs/windmill/issues/4835)) ([02611e4](https://github.com/windmill-labs/windmill/commit/02611e42e1b5c81d9bc57e3b46795e4c8cad67f7))
|
||||
|
||||
## [1.434.1](https://github.com/windmill-labs/windmill/compare/v1.434.0...v1.434.1) (2024-11-29)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET last_ping = now() WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "062859f1d0e5cfba3115f4241115753b86a4ad239708851c998ff5620ebca5b8"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*)\n FROM alerts\n WHERE COALESCE(acknowledged, false) = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "0a46f1f3047d15227f82ae24ad2113eb91d65b98927eaaba427cbde27dd79bfe"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow as \"flow!: sqlx::types::Json<Box<RawValue>>\" FROM flow_node WHERE id = $1 LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow!: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "113b120ae10ea4469ec3575dc3506aaa6d6a8940017a1172403ca9851d0f13a7"
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT lock, code AS \"code!: String\" FROM flow_node WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "lock",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "code!: String",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "15a5db0202c61ed187d9594311cb32cbde4240e143df1c980843dcdfeef3499e"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1ec97e1bf7c6edfa82b7e64585171ca897dcfa9e82618ce8f11afb08a39e3b20"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*)\n FROM alerts\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "372eb162ace15a4f07162bb46706eaca91a3616c9bfbd78a408b7079ca9706d4"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow AS \"flow!: Json<Box<JsonRawValue>>\" FROM flow_node WHERE id = $1 LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow!: Json<Box<JsonRawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3d77e5b691dab38b3e39477ed980560bacd55c6ae9fafcbb0239163a3d7f3c0c"
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH existing AS (\n SELECT id FROM flow_node\n WHERE hash = $1 AND path = $2 AND workspace_id = $3 AND code = $4 AND lock = $5 AND (flow IS NOT DISTINCT FROM $6)\n LIMIT 1\n ),\n inserted AS (\n INSERT INTO flow_node (hash, path, workspace_id, code, lock, flow)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT DO NOTHING\n RETURNING id\n )\n SELECT id FROM existing\n UNION ALL\n SELECT id FROM inserted\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5ac7c9eb3ce21e46f90e23435562b5f818a4274ba223f905e68280b86b24fa25"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts\n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true\n ELSE acknowledged_workspace\n END\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "65da41c7ded54cdee8d33211561c068b72294cc99ff44ed0a13179df508ebc6a"
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app\n (workspace_id, path, summary, policy, versions, draft_only)\n VALUES ($1, $2, $3, $4, '{}', $5) RETURNING id",
|
||||
"query": "INSERT INTO app\n (workspace_id, path, summary, policy, versions, draft_only, custom_path)\n VALUES ($1, $2, $3, $4, '{}', $5, $6) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,12 +15,13 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Bool"
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "75e880f9d9fbda36c2314706923cef36e4667d930fb8ee1876dd9ce1c92396b2"
|
||||
"hash": "6b53f7c4bb73177316d6134698f3979f51b53dcd4d8ec50d312c9e7fe31ad5f5"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts \n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7c32176755c6ea2b6ae531860d436caae3fa256fc0803749ec5107632669adb3"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts \n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2 THEN\n CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "80864158f61adaad8df934acc54ba523c9f17d106298d8781885134d28553d36"
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO flow_node (path, workspace_id, hash_v2, lock, code, flow)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (hash_v2) DO UPDATE SET path = EXCLUDED.path -- trivial update to return the id\n RETURNING id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bpchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "83cc9e432aea1450f79e9fce04eca0e75f30faa8c39ad4754bd568ae6f210806"
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT lock, code AS \"code!: String\" FROM flow_node WHERE id = $1 LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "lock",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "code!: String",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "8653373c2c8301c902e21e8bf5a11480ff040bb987a48b6f66522bc9b7e2c1d8"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*)\n FROM alerts",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "951c053cf756f00e1da26b06edb3d0193a0ee707e6482a892db58915e0c8a27f"
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH existing AS (\n SELECT id FROM flow_node\n WHERE hash = $1 AND path = $2 AND workspace_id = $3 AND code = $4 AND lock = $5 AND flow = $6\n LIMIT 1\n ),\n inserted AS (\n INSERT INTO flow_node (hash, path, workspace_id, code, lock, flow)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT DO NOTHING\n RETURNING id\n )\n SELECT id FROM existing\n UNION ALL\n SELECT id FROM inserted\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "aa98ab0e4b9a0eb41a804ab047a120444af65b1dc43a234821cde5bf6bf8b74f"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*)\n FROM alerts\n WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b9ed42d4b795942251baafd016b4361e75257c0e5faf8795274ec86236a27413"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts\n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $3 THEN\n CASE\n WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cd4067e68b375461a495f402d05976da6c6e331d5748bf6f8d59f9f75c027fe8"
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fb1399c1dc171ec6bb24fee3477a1d606d9725e20e9c2d76a0887fadfd87f8df"
|
||||
}
|
||||
Generated
+199
-188
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.434.1"
|
||||
version = "1.435.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -29,7 +29,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.434.1"
|
||||
version = "1.435.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -284,6 +284,7 @@ tikv-jemalloc-sys = { version = "^0.5" }
|
||||
tikv-jemalloc-ctl = { version = "^0.5" }
|
||||
|
||||
triomphe = "^0"
|
||||
pin-project-lite = "^0"
|
||||
|
||||
tantivy = "0.22.0"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
aefbc1e2188fea312996fcfc30a29d8fb5315316
|
||||
8606d98a692d11b09a387c5efbd6b4335c533fd3
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE app DROP COLUMN custom_path;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE app ADD COLUMN custom_path TEXT CHECK (custom_path ~ '^[\w-]+(\/[\w-]+)*$');
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
GRANT ALL ON concurrency_key TO windmill_admin;
|
||||
GRANT ALL ON concurrency_key TO windmill_user;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE flow_node DROP COLUMN hash_v2;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
CREATE SEQUENCE IF NOT EXISTS flow_node_hash_seq;
|
||||
ALTER TABLE flow_node ALTER COLUMN hash DROP NOT NULL;
|
||||
ALTER TABLE flow_node ADD COLUMN hash_v2 CHAR(64) NOT NULL UNIQUE DEFAULT to_hex(nextval('flow_node_hash_seq'));
|
||||
@@ -85,12 +85,12 @@ lazy_static::lazy_static! {
|
||||
static ref ZOMBIE_JOB_TIMEOUT: String = std::env::var("ZOMBIE_JOB_TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<String>().ok())
|
||||
.unwrap_or_else(|| "30".to_string());
|
||||
.unwrap_or_else(|| "60".to_string());
|
||||
|
||||
static ref FLOW_ZOMBIE_TRANSITION_TIMEOUT: String = std::env::var("FLOW_ZOMBIE_TRANSITION_TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<String>().ok())
|
||||
.unwrap_or_else(|| "30".to_string());
|
||||
.unwrap_or_else(|| "60".to_string());
|
||||
|
||||
|
||||
pub static ref RESTART_ZOMBIE_JOBS: bool = std::env::var("RESTART_ZOMBIE_JOBS")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.434.1
|
||||
version: 1.435.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -872,9 +872,20 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CriticalAlert'
|
||||
type: object
|
||||
properties:
|
||||
alerts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CriticalAlert'
|
||||
total_rows:
|
||||
type: integer
|
||||
description: Total number of rows matching the query.
|
||||
example: 100
|
||||
total_pages:
|
||||
type: integer
|
||||
description: Total number of pages based on the page size.
|
||||
example: 10
|
||||
|
||||
/settings/critical_alerts/{id}/acknowledge:
|
||||
post:
|
||||
@@ -2757,9 +2768,20 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CriticalAlert'
|
||||
type: object
|
||||
properties:
|
||||
alerts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CriticalAlert'
|
||||
total_rows:
|
||||
type: integer
|
||||
description: Total number of rows matching the query.
|
||||
example: 100
|
||||
total_pages:
|
||||
type: integer
|
||||
description: Total number of pages based on the page size.
|
||||
example: 10
|
||||
|
||||
/w/{workspace}/workspaces/critical_alerts/{id}/acknowledge:
|
||||
post:
|
||||
@@ -3733,6 +3755,27 @@ paths:
|
||||
required:
|
||||
- app
|
||||
|
||||
/apps_u/public_app_by_custom_path/{custom_path}:
|
||||
get:
|
||||
summary: get public app by custom path
|
||||
operationId: getPublicAppByCustomPath
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/CustomPath"
|
||||
responses:
|
||||
"200":
|
||||
description: app details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/AppWithLastVersion"
|
||||
- type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
|
||||
/scripts/hub/get/{path}:
|
||||
get:
|
||||
summary: get hub script content by path
|
||||
@@ -5371,6 +5414,8 @@ paths:
|
||||
type: boolean
|
||||
deployment_message:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -5696,6 +5741,8 @@ paths:
|
||||
$ref: "#/components/schemas/Policy"
|
||||
deployment_message:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: app updated
|
||||
@@ -5704,6 +5751,23 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/custom_path_exists/{custom_path}:
|
||||
get:
|
||||
summary: check if custom path exists
|
||||
operationId: customPathExists
|
||||
tags:
|
||||
- app
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/CustomPath"
|
||||
responses:
|
||||
"200":
|
||||
description: custom path exists
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/apps_u/execute_component/{path}:
|
||||
post:
|
||||
summary: executeComponent
|
||||
@@ -10222,6 +10286,12 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
CustomPath:
|
||||
name: custom_path
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
PathId:
|
||||
name: id
|
||||
in: path
|
||||
@@ -12860,6 +12930,8 @@ components:
|
||||
draft_only:
|
||||
type: boolean
|
||||
draft: {}
|
||||
custom_path:
|
||||
type: string
|
||||
|
||||
AppHistory:
|
||||
type: object
|
||||
|
||||
@@ -56,10 +56,10 @@ use windmill_common::{
|
||||
jobs::{get_payload_tag_from_prefixed_path, JobPayload, RawCode},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{
|
||||
http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, Pagination, StripPath,
|
||||
http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath
|
||||
},
|
||||
variables::{build_crypt, build_crypt_with_key_suffix},
|
||||
worker::to_raw_value,
|
||||
worker::{to_raw_value, CLOUD_HOSTED},
|
||||
HUB_BASE_URL,
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/history/p/*path", get(get_app_history))
|
||||
.route("/get_latest_version/*path", get(get_latest_version))
|
||||
.route("/history_update/a/:id/v/:version", post(update_app_history))
|
||||
.route("/custom_path_exists/*custom_path", get(custom_path_exists))
|
||||
}
|
||||
|
||||
pub fn unauthed_service() -> Router {
|
||||
@@ -90,13 +91,17 @@ pub fn unauthed_service() -> Router {
|
||||
.route("/public_app/:secret", get(get_public_app_by_secret))
|
||||
.route("/public_resource/*path", get(get_public_resource))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
.route("/hub/list", get(list_hub_apps))
|
||||
.route("/hub/get/:id", get(get_hub_app_by_id))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize)]
|
||||
pub struct ListableApp {
|
||||
pub id: i64,
|
||||
@@ -147,21 +152,26 @@ pub struct AppWithLastVersionAndStarred {
|
||||
pub starred: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Serialize, FromRow)]
|
||||
pub struct AppWithLastVersionAndWorkspace {
|
||||
#[sqlx(flatten)]
|
||||
#[serde(flatten)]
|
||||
pub app: AppWithLastVersion,
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRow)]
|
||||
pub struct AppWithLastVersionAndDraft {
|
||||
pub id: i64,
|
||||
pub path: String,
|
||||
pub summary: String,
|
||||
pub policy: sqlx::types::Json<Box<RawValue>>,
|
||||
pub versions: Vec<i64>,
|
||||
pub value: sqlx::types::Json<Box<RawValue>>,
|
||||
pub created_by: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub extra_perms: serde_json::Value,
|
||||
#[sqlx(flatten)]
|
||||
#[serde(flatten)]
|
||||
pub app: AppWithLastVersion,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub draft: Option<sqlx::types::Json<Box<RawValue>>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub draft_only: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -229,6 +239,7 @@ pub struct CreateApp {
|
||||
pub policy: Policy,
|
||||
pub draft_only: Option<bool>,
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -238,6 +249,7 @@ pub struct EditApp {
|
||||
pub value: Option<sqlx::types::Json<Box<RawValue>>>,
|
||||
pub policy: Option<Policy>,
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -408,7 +420,7 @@ async fn get_app_w_draft(
|
||||
|
||||
let app_o = sqlx::query_as::<_, AppWithLastVersionAndDraft>(
|
||||
r#"SELECT app.id, app.path, app.summary, app.versions, app.policy,
|
||||
app.extra_perms, app_version.value,
|
||||
app.extra_perms, app_version.value, app.custom_path,
|
||||
app_version.created_at, app_version.created_by,
|
||||
app.draft_only, draft.value as "draft"
|
||||
from app
|
||||
@@ -515,6 +527,22 @@ async fn update_app_history(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
async fn custom_path_exists(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, custom_path)): Path<(String, String)>,
|
||||
) -> JsonResult<bool> {
|
||||
let exists =
|
||||
sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
|
||||
custom_path,
|
||||
if *CLOUD_HOSTED { Some(&w_id) } else { None }
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?.unwrap_or(false);
|
||||
Ok(Json(exists))
|
||||
}
|
||||
|
||||
async fn get_app_by_id(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -598,6 +626,7 @@ async fn get_public_app_by_secret(
|
||||
Ok(Json(app))
|
||||
}
|
||||
|
||||
|
||||
async fn get_public_resource(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
@@ -680,6 +709,26 @@ async fn create_app(
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(custom_path) = &app.custom_path {
|
||||
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))",
|
||||
custom_path,
|
||||
if *CLOUD_HOSTED { Some(&w_id) } else { None }
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?.unwrap_or(false);
|
||||
|
||||
if exists {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"App with custom path {} already exists",
|
||||
custom_path
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
|
||||
&app.path,
|
||||
@@ -690,13 +739,14 @@ async fn create_app(
|
||||
|
||||
let id = sqlx::query_scalar!(
|
||||
"INSERT INTO app
|
||||
(workspace_id, path, summary, policy, versions, draft_only)
|
||||
VALUES ($1, $2, $3, $4, '{}', $5) RETURNING id",
|
||||
(workspace_id, path, summary, policy, versions, draft_only, custom_path)
|
||||
VALUES ($1, $2, $3, $4, '{}', $5, $6) RETURNING id",
|
||||
w_id,
|
||||
app.path,
|
||||
app.summary,
|
||||
json!(app.policy),
|
||||
app.draft_only,
|
||||
app.custom_path,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
@@ -899,7 +949,11 @@ async fn update_app(
|
||||
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let npath = if ns.policy.is_some() || ns.path.is_some() || ns.summary.is_some() {
|
||||
let npath = if ns.policy.is_some()
|
||||
|| ns.path.is_some()
|
||||
|| ns.summary.is_some()
|
||||
|| ns.custom_path.is_some()
|
||||
{
|
||||
let mut sqlb = SqlBuilder::update_table("app");
|
||||
sqlb.and_where_eq("path", "?".bind(&path));
|
||||
sqlb.and_where_eq("workspace_id", "?".bind(&w_id));
|
||||
@@ -932,6 +986,29 @@ async fn update_app(
|
||||
sqlb.set_str("summary", nsummary);
|
||||
}
|
||||
|
||||
if let Some(ncustom_path) = &ns.custom_path {
|
||||
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))",
|
||||
ncustom_path,
|
||||
if *CLOUD_HOSTED { Some(&w_id) } else { None },
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?.unwrap_or(false);
|
||||
|
||||
if exists {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"App with custom path {} already exists",
|
||||
ncustom_path
|
||||
)));
|
||||
}
|
||||
sqlb.set_str("custom_path", ncustom_path);
|
||||
}
|
||||
|
||||
if let Some(mut npolicy) = ns.policy {
|
||||
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
npolicy.on_behalf_of_email = Some(authed.email.clone());
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
@@ -64,6 +64,8 @@ mod indexer_ee;
|
||||
mod inputs;
|
||||
mod integration;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod apps_ee;
|
||||
#[cfg(feature = "parquet")]
|
||||
mod job_helpers_ee;
|
||||
pub mod job_metrics;
|
||||
@@ -343,6 +345,17 @@ pub async fn run_server(
|
||||
)
|
||||
.nest("/concurrency_groups", concurrency_groups::global_service())
|
||||
.nest("/scripts_u", scripts::global_unauthed_service())
|
||||
.nest("/apps_u", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
apps_ee::global_unauthed_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest(
|
||||
"/w/:workspace_id/apps_u",
|
||||
apps::unauthed_service()
|
||||
|
||||
@@ -1439,14 +1439,27 @@ async fn delete_script_by_path(
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
}
|
||||
|
||||
let script = sqlx::query_scalar!(
|
||||
"DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("deleting script by path {w_id}: {e:#}")))?;
|
||||
let script = if !draft_only {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
sqlx::query_scalar!(
|
||||
"DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("deleting script by path {w_id}: {e:#}")))?
|
||||
} else {
|
||||
// If the script is draft only, we can delete it without admin permissions but we still need write permissions
|
||||
sqlx::query_scalar!(
|
||||
"DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("deleting script by path {w_id}: {e:#}")))?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'script'",
|
||||
|
||||
@@ -448,7 +448,7 @@ pub async fn get_critical_alerts(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Query(params): Query<crate::utils::AlertQueryParams>,
|
||||
) -> JsonResult<Vec<crate::utils::CriticalAlert>> {
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
crate::utils::get_critical_alerts(db, params, None).await
|
||||
|
||||
@@ -10,6 +10,7 @@ use axum::{body::Body, response::Response};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{
|
||||
auth::{is_devops_email, is_super_admin_email},
|
||||
error::{self, Error},
|
||||
@@ -206,11 +207,55 @@ pub async fn get_critical_alerts(
|
||||
db: DB,
|
||||
params: AlertQueryParams,
|
||||
workspace_id: Option<String>,
|
||||
) -> JsonResult<Vec<CriticalAlert>> {
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
// Returning total rows and total pages
|
||||
let page = params.page.unwrap_or(1).max(1);
|
||||
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
|
||||
let offset = ((page - 1) * page_size as i32) as i64;
|
||||
|
||||
// Count total rows
|
||||
let total_rows = if let Some(workspace_id) = &workspace_id {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2",
|
||||
workspace_id,
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts"
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE COALESCE(acknowledged, false) = $1",
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch paginated rows
|
||||
let alerts = if let Some(workspace_id) = workspace_id {
|
||||
// `workspace_id` is provided => workspace admin
|
||||
if params.acknowledged.is_none() {
|
||||
@@ -278,7 +323,14 @@ pub async fn get_critical_alerts(
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(alerts))
|
||||
let total_rows = total_rows.unwrap_or(0);
|
||||
let total_pages = ((total_rows as f64) / (page_size as f64)).ceil() as i64;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"alerts": alerts,
|
||||
"total_rows": total_rows,
|
||||
"total_pages": total_pages
|
||||
})))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -292,12 +344,17 @@ pub async fn acknowledge_critical_alert(
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true
|
||||
ELSE acknowledged_workspace
|
||||
WHEN $3 THEN
|
||||
CASE
|
||||
WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE id = $1",
|
||||
id,
|
||||
workspace_id
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@@ -320,12 +377,17 @@ pub async fn acknowledge_all_critical_alerts(
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $1::text IS NOT NULL THEN true
|
||||
ELSE acknowledged_workspace
|
||||
WHEN $2 THEN
|
||||
CASE
|
||||
WHEN $1::text IS NOT NULL THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE ($1::text IS NOT NULL AND workspace_id = $1)
|
||||
OR ($1::text IS NULL)",
|
||||
workspace_id
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@@ -3111,7 +3111,7 @@ pub async fn get_critical_alerts(
|
||||
Path(w_id): Path<String>,
|
||||
authed: ApiAuthed,
|
||||
Query(params): Query<crate::utils::AlertQueryParams>,
|
||||
) -> JsonResult<Vec<crate::utils::CriticalAlert>> {
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
require_admin_or_devops(authed.is_admin, &authed.username, &authed.email, &db).await?;
|
||||
|
||||
crate::utils::get_critical_alerts(db, params, Some(w_id)).await
|
||||
|
||||
@@ -62,6 +62,7 @@ windmill-macros.workspace = true
|
||||
semver.workspace = true
|
||||
croner = "2.0.6"
|
||||
quick_cache.workspace = true
|
||||
pin-project-lite.workspace = true
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemalloc-ctl = { optional = true, workspace = true }
|
||||
|
||||
@@ -34,24 +34,36 @@ pub mod flow {
|
||||
/// If not present, import from the file-system cache or fetch it from the database and write
|
||||
/// it to the file system and cache.
|
||||
/// This should be preferred over fetching the database directly.
|
||||
pub async fn fetch_script(e: impl PgExecutor<'_>, node: FlowNodeId)
|
||||
-> error::Result<(Option<String>, String)>
|
||||
{
|
||||
fetch(e, node).await.and_then(|Val { lock, code, .. }| Ok((lock, code.ok_or_else(|| {
|
||||
error::Error::InternalErr(format!("Flow node ({:x}) isn't a script node.", node.0))
|
||||
})?)))
|
||||
pub async fn fetch_script(
|
||||
e: impl PgExecutor<'_>,
|
||||
node: FlowNodeId,
|
||||
) -> error::Result<(Option<String>, String)> {
|
||||
fetch(e, node).await.and_then(|Val { lock, code, .. }| {
|
||||
Ok((
|
||||
lock,
|
||||
code.ok_or_else(|| {
|
||||
error::Error::InternalErr(format!(
|
||||
"Flow node ({:x}) isn't a script node.",
|
||||
node.0
|
||||
))
|
||||
})?,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch the flow node flow value referenced by `node` from the cache.
|
||||
/// If not present, import from the file-system cache or fetch it from the database and write
|
||||
/// it to the file system and cache.
|
||||
/// This should be preferred over fetching the database directly.
|
||||
pub async fn fetch_flow(e: impl PgExecutor<'_>, node: FlowNodeId)
|
||||
-> error::Result<FlowValue>
|
||||
{
|
||||
fetch(e, node).await.and_then(|Val { flow, .. }| flow.ok_or_else(|| {
|
||||
error::Error::InternalErr(format!("Flow node ({:x}) isn't a flow value node.", node.0))
|
||||
}))
|
||||
pub async fn fetch_flow(e: impl PgExecutor<'_>, node: FlowNodeId) -> error::Result<FlowValue> {
|
||||
fetch(e, node).await.and_then(|Val { flow, .. }| {
|
||||
flow.ok_or_else(|| {
|
||||
error::Error::InternalErr(format!(
|
||||
"Flow node ({:x}) isn't a flow value node.",
|
||||
node.0
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch the flow node referenced by `node` from the cache.
|
||||
@@ -62,32 +74,40 @@ pub mod flow {
|
||||
// If not present, `get_or_insert_async` will lock the key until the future completes,
|
||||
// so only one thread will be able to fetch the data from the database and write it to
|
||||
// the file system and cache, hence no race on the file system.
|
||||
CACHE.get_or_insert_async(
|
||||
&node,
|
||||
fs::import_or_insert_with(CACHE_DIR, node.0 as u64, async {
|
||||
sqlx::query!(
|
||||
"SELECT \
|
||||
CACHE
|
||||
.get_or_insert_async(
|
||||
&node,
|
||||
fs::import_or_insert_with(CACHE_DIR, node.0 as u64, async {
|
||||
sqlx::query!(
|
||||
"SELECT \
|
||||
lock AS \"lock: String\", \
|
||||
code AS \"code: String\", \
|
||||
flow::text AS \"flow: Box<str>\" \
|
||||
FROM flow_node WHERE id = $1 LIMIT 1",
|
||||
node.0,
|
||||
)
|
||||
.fetch_one(e)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.and_then(|r| Ok(Val {
|
||||
lock: r.lock.and_then(|x| if x.is_empty() { None } else { Some(x) }),
|
||||
code: r.code,
|
||||
flow: match r.flow {
|
||||
None => None,
|
||||
Some(flow) => serde_json::from_str(&flow).map_err(|err| {
|
||||
error::Error::InternalErr(format!("Unable to parse flow value: {err:?}"))
|
||||
})?,
|
||||
}
|
||||
}))
|
||||
})
|
||||
).await
|
||||
node.0,
|
||||
)
|
||||
.fetch_one(e)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.and_then(|r| {
|
||||
Ok(Val {
|
||||
lock: r
|
||||
.lock
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(x) }),
|
||||
code: r.code,
|
||||
flow: match r.flow {
|
||||
None => None,
|
||||
Some(flow) => serde_json::from_str(&flow).map_err(|err| {
|
||||
error::Error::InternalErr(format!(
|
||||
"Unable to parse flow value: {err:?}"
|
||||
))
|
||||
})?,
|
||||
},
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
@@ -130,7 +150,11 @@ pub mod flow {
|
||||
match item {
|
||||
Item::Lock => Ok(self.lock.as_ref().map(|s| s.as_bytes().to_vec())),
|
||||
Item::Code => Ok(self.code.as_ref().map(|s| s.as_bytes().to_vec())),
|
||||
Item::Flow => Ok(self.flow.as_ref().map(|f| serde_json::to_vec(f)).transpose()?),
|
||||
Item::Flow => Ok(self
|
||||
.flow
|
||||
.as_ref()
|
||||
.map(|f| serde_json::to_vec(f))
|
||||
.transpose()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,39 +188,44 @@ pub mod script {
|
||||
/// If not present, import from the file-system cache or fetch it from the database and write
|
||||
/// it to the file system and cache.
|
||||
/// This should be preferred over fetching the database directly.
|
||||
pub async fn fetch(e: impl PgExecutor<'_>, hash: ScriptHash, workspace_id: &str)
|
||||
-> error::Result<Val>
|
||||
{
|
||||
pub async fn fetch(
|
||||
e: impl PgExecutor<'_>,
|
||||
hash: ScriptHash,
|
||||
workspace_id: &str,
|
||||
) -> error::Result<Val> {
|
||||
// If not present, `get_or_insert_async` will lock the key until the future completes,
|
||||
// so only one thread will be able to fetch the data from the database and write it to
|
||||
// the file system and cache, hence no race on the file system.
|
||||
CACHE.get_or_insert_async(
|
||||
&hash,
|
||||
fs::import_or_insert_with(CACHE_DIR, hash.0 as u64, async {
|
||||
sqlx::query!(
|
||||
"SELECT \
|
||||
CACHE
|
||||
.get_or_insert_async(
|
||||
&hash,
|
||||
fs::import_or_insert_with(CACHE_DIR, hash.0 as u64, async {
|
||||
sqlx::query!(
|
||||
"SELECT \
|
||||
lock AS \"lock: String\", \
|
||||
content AS \"code!: String\",
|
||||
language AS \"language: Option<ScriptLang>\", \
|
||||
envs AS \"envs: Vec<String>\", \
|
||||
codebase AS \"codebase: String\" \
|
||||
FROM script WHERE hash = $1 AND workspace_id = $2 LIMIT 1",
|
||||
hash.0,
|
||||
workspace_id,
|
||||
)
|
||||
.fetch_one(e)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|r| Val {
|
||||
lock: r.lock.and_then(|x| if x.is_empty() { None } else { Some(x) }),
|
||||
code: r.code,
|
||||
language: r.language,
|
||||
envs: r.envs,
|
||||
codebase: r.codebase,
|
||||
})
|
||||
})
|
||||
)
|
||||
.await
|
||||
hash.0,
|
||||
workspace_id,
|
||||
)
|
||||
.fetch_one(e)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|r| Val {
|
||||
lock: r
|
||||
.lock
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(x) }),
|
||||
code: r.code,
|
||||
language: r.language,
|
||||
envs: r.envs,
|
||||
codebase: r.codebase,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
@@ -230,7 +259,9 @@ pub mod script {
|
||||
match item {
|
||||
Item::Lock => self.lock = Some(String::from_utf8(data)?),
|
||||
Item::Code => self.code = String::from_utf8(data)?,
|
||||
Item::Info => (self.language, self.envs, self.codebase) = serde_json::from_slice(&data)?,
|
||||
Item::Info => {
|
||||
(self.language, self.envs, self.codebase) = serde_json::from_slice(&data)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -239,7 +270,11 @@ pub mod script {
|
||||
match item {
|
||||
Item::Lock => Ok(self.lock.as_ref().map(|s| s.as_bytes().to_vec())),
|
||||
Item::Code => Ok(Some(self.code.as_bytes().to_vec())),
|
||||
Item::Info => Ok(Some(serde_json::to_vec(&(&self.language, &self.envs, &self.codebase))?)),
|
||||
Item::Info => Ok(Some(serde_json::to_vec(&(
|
||||
&self.language,
|
||||
&self.envs,
|
||||
&self.codebase,
|
||||
))?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,8 +307,7 @@ mod fs {
|
||||
}
|
||||
|
||||
/// Import or insert a bundle within the given combination of `{root}/{key}/`.
|
||||
pub async fn import_or_insert_with<T, F>(root: &str, key: u64, f: F)
|
||||
-> error::Result<T>
|
||||
pub async fn import_or_insert_with<T, F>(root: &str, key: u64, f: F) -> error::Result<T>
|
||||
where
|
||||
T: Bundle,
|
||||
F: Future<Output = error::Result<T>>,
|
||||
@@ -287,8 +321,9 @@ mod fs {
|
||||
let mut data = T::default();
|
||||
for item in T::items() {
|
||||
let mut buf = vec![];
|
||||
let Ok(mut file) = OpenOptions::new().read(true).open(item.path(&path))
|
||||
else { continue };
|
||||
let Ok(mut file) = OpenOptions::new().read(true).open(item.path(&path)) else {
|
||||
continue;
|
||||
};
|
||||
file.read_to_end(&mut buf)?;
|
||||
data.import(*item, buf)?;
|
||||
}
|
||||
@@ -299,7 +334,7 @@ mod fs {
|
||||
Ok(data) => return Ok(data),
|
||||
Err(err) => tracing::warn!(
|
||||
"Failed to import from file-system, fetch source..: {path:?}: {err:?}"
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
// Cache path doesn't exist or import failed, generate the content.
|
||||
@@ -308,9 +343,13 @@ mod fs {
|
||||
fs::create_dir_all(&path)?;
|
||||
// Write the generated data to the file.
|
||||
for item in T::items() {
|
||||
let Some(buf) = data.export(*item)?
|
||||
else { continue };
|
||||
let mut file = OpenOptions::new().write(true).create(true).open(item.path(&path))?;
|
||||
let Some(buf) = data.export(*item)? else {
|
||||
continue;
|
||||
};
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(item.path(&path))?;
|
||||
file.write_all(&buf)?;
|
||||
}
|
||||
tracing::debug!("Exported to file-system: {:?}", path);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{db::DB, error};
|
||||
use crate::{db::DB, error, utils::WarnAfterExt};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
@@ -90,6 +90,7 @@ pub async fn register_metric_for_job(
|
||||
.bind(timeseries_int)
|
||||
.bind(timeseries_float)
|
||||
.execute(db)
|
||||
.warn_after_seconds(1)
|
||||
.await?;
|
||||
|
||||
Ok(metric_id)
|
||||
|
||||
@@ -34,8 +34,8 @@ pub const GIT_VERSION: &str =
|
||||
git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
|
||||
use crate::CRITICAL_ALERT_MUTE_UI_ENABLED;
|
||||
use std::panic::{self, AssertUnwindSafe, Location};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
|
||||
use crate::worker::CLOUD_HOSTED;
|
||||
|
||||
@@ -78,14 +78,18 @@ pub fn require_admin(is_admin: bool, username: &str) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn require_admin_or_devops(is_admin: bool, username: &str, email: &str, db: &DB) -> Result<()> {
|
||||
pub async fn require_admin_or_devops(
|
||||
is_admin: bool,
|
||||
username: &str,
|
||||
email: &str,
|
||||
db: &DB,
|
||||
) -> Result<()> {
|
||||
if !is_admin {
|
||||
if !is_devops_email(db, email).await? {
|
||||
return Err(Error::RequireAdmin(username.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn hostname() -> String {
|
||||
@@ -94,7 +98,7 @@ pub fn hostname() -> String {
|
||||
.to_str()
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_else(|| rd_string(5))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn paginate(pagination: Pagination) -> (usize, usize) {
|
||||
@@ -440,9 +444,7 @@ impl ScheduleType {
|
||||
Some("v2") | Some(_) => {
|
||||
// Use Croner for v2
|
||||
let schedule_type_result = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
Cron::new(schedule_str)
|
||||
.with_seconds_optional()
|
||||
.parse()
|
||||
Cron::new(schedule_str).with_seconds_optional().parse()
|
||||
}))
|
||||
.map_err(|_| {
|
||||
tracing::error!(
|
||||
@@ -478,8 +480,14 @@ impl ScheduleType {
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
tracing::error!("An error occurred while finding the next occurrence: {:?}", e);
|
||||
return Err(Error::BadRequest(format!("cron: error during find_next_occurrence: {:?}", e)));
|
||||
tracing::error!(
|
||||
"An error occurred while finding the next occurrence: {:?}",
|
||||
e
|
||||
);
|
||||
return Err(Error::BadRequest(format!(
|
||||
"cron: error during find_next_occurrence: {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,3 +534,78 @@ impl ScheduleType {
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context as TContext, Poll};
|
||||
use tokio::time::{self, Duration, Sleep};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pub trait WarnAfterExt: Future + Sized {
|
||||
/// Warns if the future takes longer than the specified number of seconds to complete.
|
||||
#[track_caller]
|
||||
fn warn_after_seconds(self, seconds: u8) -> WarnAfterFuture<Self> {
|
||||
let caller = Location::caller();
|
||||
let location = format!("{}:{}", caller.file(), caller.line());
|
||||
WarnAfterFuture {
|
||||
future: self,
|
||||
timeout: time::sleep(Duration::from_secs(seconds as u64)),
|
||||
warned: false,
|
||||
start_time: std::time::Instant::now(),
|
||||
location: location,
|
||||
seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blanket implementation for all futures.
|
||||
impl<F: Future> WarnAfterExt for F {}
|
||||
|
||||
pin_project! {
|
||||
/// A future that wraps another future and prints a warning if it takes too long.
|
||||
pub struct WarnAfterFuture<F> {
|
||||
#[pin]
|
||||
future: F,
|
||||
#[pin]
|
||||
timeout: Sleep,
|
||||
warned: bool,
|
||||
location: String,
|
||||
start_time: std::time::Instant,
|
||||
seconds: u8,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for WarnAfterFuture<F> {
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut TContext<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
// Poll the timeout future to check if it has elapsed.
|
||||
if !*this.warned {
|
||||
if this.timeout.poll(cx).is_ready() {
|
||||
tracing::warn!(location = this.location, "SLOW_QUERY: query to db taking longer than expected (> {} seconds). This is a sign the database is under heavy load, query is too heavy or database is undersized",
|
||||
this.seconds,
|
||||
);
|
||||
*this.warned = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the wrapped future.
|
||||
match this.future.poll(cx) {
|
||||
Poll::Ready(output) => {
|
||||
if *this.warned {
|
||||
let elapsed = this.start_time.elapsed();
|
||||
tracing::warn!(
|
||||
location = this.location,
|
||||
"SLOW_QUERY: completed with total duration: {:.2?}",
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
Poll::Ready(output)
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
|
||||
use windmill_common::{
|
||||
cache,
|
||||
auth::{fetch_authed_from_permissioned_as, permissioned_as_to_username},
|
||||
cache,
|
||||
db::{Authed, UserDB},
|
||||
error::{self, to_anyhow, Error},
|
||||
flow_status::{
|
||||
@@ -57,7 +57,7 @@ use windmill_common::{
|
||||
schedule::Schedule,
|
||||
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
|
||||
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
|
||||
utils::{not_found_if_none, report_critical_error, StripPath},
|
||||
utils::{not_found_if_none, report_critical_error, StripPath, WarnAfterExt},
|
||||
worker::{
|
||||
to_raw_value, CLOUD_HOSTED, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES,
|
||||
DISABLE_FLOW_SCRIPT, MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432, NO_LOGS,
|
||||
@@ -318,6 +318,7 @@ pub async fn append_logs(
|
||||
workspace.as_ref(),
|
||||
)
|
||||
.execute(db.borrow())
|
||||
.warn_after_seconds(1)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%job_id, %err, "error updating logs for large_log job {job_id}: {err}");
|
||||
@@ -3227,7 +3228,7 @@ pub async fn push<'c, 'd>(
|
||||
None,
|
||||
None,
|
||||
)
|
||||
},
|
||||
}
|
||||
JobPayload::ScriptHub { path } => {
|
||||
if path == "hub/7771/slack" || path == "hub/7836/slack" {
|
||||
permissioned_as = SUPERADMIN_NOTIFICATION_EMAIL.to_string();
|
||||
@@ -3851,6 +3852,7 @@ pub async fn push<'c, 'd>(
|
||||
tag,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.warn_after_seconds(1)
|
||||
.await?;
|
||||
|
||||
let (raw_code, raw_lock, raw_flow) = if !*MIN_VERSION_IS_AT_LEAST_1_427.read().await {
|
||||
@@ -3901,6 +3903,7 @@ pub async fn push<'c, 'd>(
|
||||
final_priority,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.warn_after_seconds(1)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Could not insert into queue {job_id} with tag {tag}, schedule_path {schedule_path:?}, script_path: {script_path:?}, email {email}, workspace_id {workspace_id}: {e:#}")))?;
|
||||
|
||||
|
||||
@@ -433,6 +433,10 @@ fi
|
||||
.args(cmd_args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
#[cfg(windows)]
|
||||
ansible_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
|
||||
start_child_process(ansible_cmd, ANSIBLE_PLAYBOOK_PATH.as_str()).await?
|
||||
};
|
||||
|
||||
@@ -565,13 +569,9 @@ async fn create_file_resources(
|
||||
get_resource_or_variable_content(client, &file_res.resource_path, job_id.to_string())
|
||||
.await?;
|
||||
let path = file_res.target_path.clone();
|
||||
let validated_path = write_file_at_user_defined_location(
|
||||
job_dir,
|
||||
path.as_str(),
|
||||
&r,
|
||||
file_res.mode,
|
||||
)
|
||||
.map_err(|e| anyhow!("Couldn't write text file at {}: {}", path, e))?;
|
||||
let validated_path =
|
||||
write_file_at_user_defined_location(job_dir, path.as_str(), &r, file_res.mode)
|
||||
.map_err(|e| anyhow!("Couldn't write text file at {}: {}", path, e))?;
|
||||
|
||||
nsjail_mounts.push(
|
||||
define_nsjail_mount(job_dir, &validated_path)
|
||||
|
||||
@@ -522,7 +522,8 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
|
||||
std::env::var("PATHEXT").unwrap_or_else(|_| {
|
||||
String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL")
|
||||
}),
|
||||
);
|
||||
)
|
||||
.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
}
|
||||
|
||||
cmd.spawn()?
|
||||
|
||||
@@ -1522,6 +1522,15 @@ pub async fn get_common_bun_proc_envs(base_internal_url: Option<&str>) -> HashMa
|
||||
bun_envs.insert(String::from("NODE_PATH"), node_path.to_string());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
bun_envs.insert("SystemRoot".to_string(), crate::SYSTEM_ROOT.to_string());
|
||||
bun_envs.insert(
|
||||
"USERPROFILE".to_string(),
|
||||
crate::USERPROFILE_ENV.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
return bun_envs;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,15 @@ async fn get_common_deno_proc_envs(
|
||||
if DENO_TLS_CA_STORE.len() > 0 {
|
||||
deno_envs.insert(String::from("DENO_TLS_CA_STORE"), DENO_TLS_CA_STORE.clone());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
deno_envs.insert("SystemRoot".to_string(), crate::SYSTEM_ROOT.to_string());
|
||||
deno_envs.insert(
|
||||
"USERPROFILE".to_string(),
|
||||
crate::USERPROFILE_ENV.to_string(),
|
||||
);
|
||||
}
|
||||
return deno_envs;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,10 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.args(vec!["build", "main.go"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
#[cfg(windows)]
|
||||
build_go_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
|
||||
let build_go_process = start_child_process(build_go_cmd, GO_PATH.as_str()).await?;
|
||||
handle_child(
|
||||
&job.id,
|
||||
@@ -293,6 +297,9 @@ func Run(req Req) (interface{{}}, error){{
|
||||
run_go.env("GOPROXY", goproxy);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
run_go.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
|
||||
run_go.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
start_child_process(run_go, compiled_executable_name).await?
|
||||
};
|
||||
|
||||
@@ -8,7 +8,14 @@ use windmill_common::DB;
|
||||
use crate::job_logger::CompactLogs;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub(crate) async fn s3_storage(_job_id: Uuid, _w_id: &String, _db: &sqlx::Pool<sqlx::Postgres>, _logs: &String, _total_size: &Arc<AtomicU32>, _worker_name: &String) {
|
||||
pub(crate) async fn s3_storage(
|
||||
_job_id: Uuid,
|
||||
_w_id: &String,
|
||||
_db: &sqlx::Pool<sqlx::Postgres>,
|
||||
_logs: &String,
|
||||
_total_size: &Arc<AtomicU32>,
|
||||
_worker_name: &String,
|
||||
) {
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on s3 in not OSS");
|
||||
}
|
||||
|
||||
@@ -24,7 +31,9 @@ pub(crate) async fn default_disk_log_storage(
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on disk in not OSS");
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn process_streaming_log_lines(r: Result<Option<String>, io::Error>, _stderr: bool) -> Option<Result<String, io::Error>> {
|
||||
pub(crate) fn process_streaming_log_lines(
|
||||
r: Result<Option<String>, io::Error>,
|
||||
_stderr: bool,
|
||||
) -> Option<Result<String, io::Error>> {
|
||||
r.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,8 +749,10 @@ mount {{
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
#[cfg(windows)]
|
||||
python_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
|
||||
#[cfg(windows)] {
|
||||
python_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
|
||||
python_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
}
|
||||
|
||||
start_child_process(python_cmd, PYTHON_PATH.as_str()).await?
|
||||
};
|
||||
@@ -1277,6 +1279,7 @@ async fn spawn_uv_install(
|
||||
.envs(envs)
|
||||
.envs(PROXY_ENVS.clone())
|
||||
.env("SystemRoot", SYSTEM_ROOT.as_str())
|
||||
.env("USERPROFILE", crate::USERPROFILE_ENV.as_str())
|
||||
.env(
|
||||
"TMP",
|
||||
std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")),
|
||||
|
||||
@@ -14,6 +14,7 @@ use windmill_common::{
|
||||
add_time,
|
||||
error::{self, Error},
|
||||
jobs::{JobKind, QueuedJob},
|
||||
utils::WarnAfterExt,
|
||||
worker::{to_raw_value, WORKER_GROUP},
|
||||
DB,
|
||||
};
|
||||
@@ -431,6 +432,7 @@ pub async fn process_completed_job(
|
||||
#[cfg(feature = "benchmark")]
|
||||
bench,
|
||||
)
|
||||
.warn_after_seconds(10)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -469,6 +471,7 @@ pub async fn process_completed_job(
|
||||
#[cfg(feature = "benchmark")]
|
||||
bench,
|
||||
)
|
||||
.warn_after_seconds(10)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ pub async fn build_rust_crate(
|
||||
"TMP",
|
||||
std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()),
|
||||
);
|
||||
build_rust_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
}
|
||||
|
||||
let build_rust_process = start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await?;
|
||||
@@ -402,7 +403,10 @@ pub async fn handle_rust_job(
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
#[cfg(windows)]
|
||||
run_rust.env("SystemRoot", SYSTEM_ROOT.as_str());
|
||||
{
|
||||
run_rust.env("SystemRoot", SYSTEM_ROOT.as_str());
|
||||
run_rust.env("USERPROFILE", crate::USERPROFILE_ENV.as_str());
|
||||
}
|
||||
|
||||
start_child_process(run_rust, compiled_executable_name).await?
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
use windmill_common::{
|
||||
auth::{fetch_authed_from_permissioned_as, JWTAuthClaims, JobPerms, JWT_SECRET},
|
||||
scripts::PREVIEW_IS_TAR_CODEBASE_HASH,
|
||||
utils::WarnAfterExt,
|
||||
worker::{
|
||||
get_memory, get_vcpus, get_windmill_memory_usage, get_worker_memory_usage, write_file,
|
||||
ROOT_CACHE_DIR, TMP_DIR,
|
||||
@@ -159,6 +160,7 @@ pub async fn create_token_for_owner_in_bg(
|
||||
&email,
|
||||
&job_id,
|
||||
)
|
||||
.warn_after_seconds(5)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
*locked = token;
|
||||
@@ -366,6 +368,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref NSJAIL_PATH: String = std::env::var("NSJAIL_PATH").unwrap_or_else(|_| "nsjail".to_string());
|
||||
pub static ref PATH_ENV: String = std::env::var("PATH").unwrap_or_else(|_| String::new());
|
||||
pub static ref HOME_ENV: String = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
|
||||
pub static ref NODE_PATH: Option<String> = std::env::var("NODE_PATH").ok();
|
||||
|
||||
pub static ref TZ_ENV: String = std::env::var("TZ").unwrap_or_else(|_| String::new());
|
||||
@@ -419,12 +422,12 @@ lazy_static::lazy_static! {
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SYSTEM_ROOT: String = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
|
||||
pub static ref USERPROFILE_ENV: String = std::env::var("USERPROFILE").unwrap_or_else(|_| "/tmp".to_string());
|
||||
}
|
||||
|
||||
//only matter if CLOUD_HOSTED
|
||||
@@ -1823,7 +1826,9 @@ async fn handle_queued_job(
|
||||
if job.parent_job.is_none() && job.created_by.starts_with("email-") {
|
||||
let daily_count = sqlx::query!(
|
||||
"SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1"
|
||||
).fetch_optional(db).await?.map(|x| serde_json::from_value::<i64>(x.value).unwrap_or(1));
|
||||
).fetch_optional(db)
|
||||
.warn_after_seconds(5)
|
||||
.await?.map(|x| serde_json::from_value::<i64>(x.value).unwrap_or(1));
|
||||
|
||||
if let Some(count) = daily_count {
|
||||
if count >= 100 {
|
||||
@@ -1836,6 +1841,7 @@ async fn handle_queued_job(
|
||||
serde_json::json!(count + 1)
|
||||
)
|
||||
.execute(db)
|
||||
.warn_after_seconds(5)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
@@ -1843,6 +1849,7 @@ async fn handle_queued_job(
|
||||
"INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))"
|
||||
)
|
||||
.execute(db)
|
||||
.warn_after_seconds(5)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -1855,6 +1862,7 @@ async fn handle_queued_job(
|
||||
.ok_or_else(|| Error::InternalErr(format!("expected parent job")))?,
|
||||
job.id,
|
||||
)
|
||||
.warn_after_seconds(5)
|
||||
.await?;
|
||||
|
||||
Some(r)
|
||||
@@ -1867,6 +1875,7 @@ async fn handle_queued_job(
|
||||
&job.workspace_id
|
||||
)
|
||||
.execute(db)
|
||||
.warn_after_seconds(5)
|
||||
.await {
|
||||
tracing::error!("Could not update parent job started_at flow_status: {}", e);
|
||||
}
|
||||
@@ -1883,6 +1892,7 @@ async fn handle_queued_job(
|
||||
job.workspace_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.warn_after_seconds(5)
|
||||
.await
|
||||
.map(|record| (record.raw_code, record.raw_lock, record.raw_flow))
|
||||
.unwrap_or_default(),
|
||||
@@ -1923,6 +1933,7 @@ async fn handle_queued_job(
|
||||
&job.parent_job.unwrap()
|
||||
)
|
||||
.fetch_one(db)
|
||||
.warn_after_seconds(5)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
@@ -1957,6 +1968,7 @@ async fn handle_queued_job(
|
||||
&job.workspace_id,
|
||||
&cached_res_path,
|
||||
)
|
||||
.warn_after_seconds(5)
|
||||
.await;
|
||||
if let Some(cached_resource_value) = cached_resource_value_maybe {
|
||||
{
|
||||
@@ -1995,6 +2007,7 @@ async fn handle_queued_job(
|
||||
worker_dir,
|
||||
job_completed_tx.0.clone(),
|
||||
)
|
||||
.warn_after_seconds(10)
|
||||
.await?;
|
||||
Ok(true)
|
||||
} else {
|
||||
@@ -2285,9 +2298,11 @@ async fn handle_code_execution_job(
|
||||
.await?
|
||||
}
|
||||
JobKind::FlowScript => {
|
||||
let (lockfile, content) = cache::flow::fetch_script(db, FlowNodeId(
|
||||
job.script_hash.unwrap_or(ScriptHash(0)).0
|
||||
)).await?;
|
||||
let (lockfile, content) = cache::flow::fetch_script(
|
||||
db,
|
||||
FlowNodeId(job.script_hash.unwrap_or(ScriptHash(0)).0),
|
||||
)
|
||||
.await?;
|
||||
ContentReqLangEnvs {
|
||||
content,
|
||||
lockfile,
|
||||
|
||||
@@ -41,6 +41,7 @@ use windmill_common::jobs::{
|
||||
script_hash_to_tag_and_limits, script_path_to_payload, BranchResults, JobPayload, QueuedJob,
|
||||
RawCode, ENTRYPOINT_OVERRIDE,
|
||||
};
|
||||
use windmill_common::utils::WarnAfterExt;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
use windmill_common::{
|
||||
error::{self, to_anyhow, Error},
|
||||
@@ -1109,6 +1110,7 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
worker_dir,
|
||||
job_completed_tx,
|
||||
)
|
||||
.warn_after_seconds(10)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
@@ -1510,7 +1512,7 @@ pub async fn handle_flow(
|
||||
let schedule_path = flow_job.schedule_path.as_ref().unwrap();
|
||||
|
||||
let schedule =
|
||||
get_schedule_opt(&mut tx, &flow_job.workspace_id, schedule_path).await?;
|
||||
get_schedule_opt(&mut tx, &flow_job.workspace_id, schedule_path).warn_after_seconds(5).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -1522,6 +1524,7 @@ pub async fn handle_flow(
|
||||
flow_job.script_path.as_ref().unwrap(),
|
||||
&flow_job.workspace_id,
|
||||
)
|
||||
.warn_after_seconds(5)
|
||||
.await
|
||||
{
|
||||
match err {
|
||||
@@ -1549,6 +1552,7 @@ pub async fn handle_flow(
|
||||
worker_dir,
|
||||
job_completed_tx,
|
||||
)
|
||||
.warn_after_seconds(10)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2133,6 +2137,7 @@ async fn push_next_flow_job(
|
||||
.bind(status.step)
|
||||
.bind(json!(status.retry.failed_jobs))
|
||||
.execute(db)
|
||||
.warn_after_seconds(2)
|
||||
.await
|
||||
.context("update flow retry")?;
|
||||
|
||||
@@ -2558,6 +2563,7 @@ async fn push_next_flow_job(
|
||||
new_job_priority_override,
|
||||
job_perms.as_ref(),
|
||||
)
|
||||
.warn_after_seconds(2)
|
||||
.await?;
|
||||
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "pushed next flow job: {uuid}");
|
||||
@@ -2770,7 +2776,7 @@ async fn push_next_flow_job(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
tx.commit().warn_after_seconds(3).await?;
|
||||
tracing::info!(id = %flow_job.id, root_id = %job_root, "all next flow jobs pushed: {uuids:?}");
|
||||
|
||||
if continue_on_same_worker {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::path::{Component, Path, PathBuf};
|
||||
use async_recursion::async_recursion;
|
||||
use serde_json::value::RawValue;
|
||||
use serde_json::{json, Value};
|
||||
use sha2::Digest;
|
||||
use sqlx::types::Json;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::error::Error;
|
||||
@@ -606,9 +607,8 @@ pub async fn handle_flow_dependency_job(
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await?;
|
||||
let new_flow_value = sqlx::types::Json(
|
||||
serde_json::value::to_raw_value(&flow).map_err(to_anyhow)?
|
||||
);
|
||||
let new_flow_value =
|
||||
sqlx::types::Json(serde_json::value::to_raw_value(&flow).map_err(to_anyhow)?);
|
||||
|
||||
// Re-check cancelation to ensure we don't accidentially override a flow.
|
||||
if sqlx::query_scalar!("SELECT canceled FROM queue WHERE id = $1", job.id)
|
||||
@@ -648,11 +648,20 @@ pub async fn handle_flow_dependency_job(
|
||||
|
||||
// Compute a lite version of the flow value (`RawScript` => `FlowScript`).
|
||||
let mut value_lite = flow.clone();
|
||||
tx = reduce(tx, &mut value_lite.modules, &job_path, &job.workspace_id, flow.failure_module.as_ref(), flow.same_worker).await?;
|
||||
tx = reduce(
|
||||
tx,
|
||||
&mut value_lite.modules,
|
||||
&job_path,
|
||||
&job.workspace_id,
|
||||
flow.failure_module.as_ref(),
|
||||
flow.same_worker,
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO flow_version_lite (id, value) VALUES ($1, $2)
|
||||
ON CONFLICT (id) DO UPDATE SET value = EXCLUDED.value",
|
||||
version, sqlx::types::Json(to_raw_value(&value_lite)) as sqlx::types::Json<Box<RawValue>>,
|
||||
version,
|
||||
sqlx::types::Json(to_raw_value(&value_lite)) as sqlx::types::Json<Box<RawValue>>,
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
@@ -829,8 +838,12 @@ async fn lock_modules<'c>(
|
||||
occupancy_metrics,
|
||||
))
|
||||
.await?;
|
||||
e.value =
|
||||
FlowModuleValue::WhileloopFlow { modules: nmodules, modules_node, skip_failures }.into()
|
||||
e.value = FlowModuleValue::WhileloopFlow {
|
||||
modules: nmodules,
|
||||
modules_node,
|
||||
skip_failures,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
FlowModuleValue::BranchOne { branches, default, default_node } => {
|
||||
let mut nbranches = vec![];
|
||||
@@ -878,8 +891,12 @@ async fn lock_modules<'c>(
|
||||
occupancy_metrics,
|
||||
))
|
||||
.await?;
|
||||
e.value = FlowModuleValue::BranchOne { branches: nbranches, default: ndefault, default_node }
|
||||
.into();
|
||||
e.value = FlowModuleValue::BranchOne {
|
||||
branches: nbranches,
|
||||
default: ndefault,
|
||||
default_node,
|
||||
}
|
||||
.into();
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
@@ -1009,41 +1026,32 @@ async fn insert_flow_node<'c>(
|
||||
flow: Option<&Json<Box<RawValue>>>,
|
||||
) -> Result<(sqlx::Transaction<'c, sqlx::Postgres>, FlowNodeId)> {
|
||||
let hash = {
|
||||
use std::hash::{DefaultHasher, Hasher, Hash};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
code.hash(&mut hasher);
|
||||
lock.hash(&mut hasher);
|
||||
flow.inspect(|flow| flow.get().hash(&mut hasher));
|
||||
hasher.finish() as i64
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(path);
|
||||
hasher.update(workspace_id);
|
||||
hasher.update(code.unwrap_or(&Default::default()));
|
||||
hasher.update(lock.unwrap_or(&Default::default()));
|
||||
hasher.update(flow.unwrap_or(&Default::default()).get());
|
||||
format!("{:x}", hasher.finalize())
|
||||
};
|
||||
|
||||
// Insert the flow node if it doesn't exist.
|
||||
let id = sqlx::query_scalar!(
|
||||
r#"
|
||||
WITH existing AS (
|
||||
SELECT id FROM flow_node
|
||||
WHERE hash = $1 AND path = $2 AND workspace_id = $3
|
||||
AND (code IS NOT DISTINCT FROM $4)
|
||||
AND (lock IS NOT DISTINCT FROM $5)
|
||||
AND (flow IS NOT DISTINCT FROM $6)
|
||||
LIMIT 1
|
||||
),
|
||||
inserted AS (
|
||||
INSERT INTO flow_node (hash, path, workspace_id, code, lock, flow)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING id
|
||||
)
|
||||
SELECT id FROM existing
|
||||
UNION ALL
|
||||
SELECT id FROM inserted
|
||||
INSERT INTO flow_node (path, workspace_id, hash_v2, lock, code, flow)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (hash_v2) DO UPDATE SET path = EXCLUDED.path -- trivial update to return the id
|
||||
RETURNING id
|
||||
"#,
|
||||
hash, path, workspace_id, code, lock, flow as Option<&Json<Box<RawValue>>>
|
||||
path,
|
||||
workspace_id,
|
||||
hash,
|
||||
lock,
|
||||
code,
|
||||
flow as Option<&Json<Box<RawValue>>>
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
.ok_or(error::Error::InternalErr("Failed to cache".to_string()))?;
|
||||
.await?;
|
||||
Ok((tx, FlowNodeId(id)))
|
||||
}
|
||||
|
||||
@@ -1056,7 +1064,15 @@ async fn insert_flow_modules<'c>(
|
||||
modules: &mut Vec<FlowModule>,
|
||||
modules_node: &mut Option<FlowNodeId>,
|
||||
) -> Result<sqlx::Transaction<'c, sqlx::Postgres>> {
|
||||
tx = Box::pin(reduce(tx, modules, path, workspace_id, failure_module, same_worker)).await?;
|
||||
tx = Box::pin(reduce(
|
||||
tx,
|
||||
modules,
|
||||
path,
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
))
|
||||
.await?;
|
||||
add_virtual_items_if_necessary(modules);
|
||||
if modules.is_empty() || crate::worker_flow::is_simple_modules(modules, failure_module) {
|
||||
return Ok(tx);
|
||||
@@ -1073,7 +1089,7 @@ async fn insert_flow_modules<'c>(
|
||||
failure_module: failure_module.cloned(),
|
||||
same_worker,
|
||||
..Default::default()
|
||||
})))
|
||||
}))),
|
||||
)
|
||||
.await?;
|
||||
*modules_node = Some(id);
|
||||
@@ -1090,8 +1106,13 @@ async fn reduce<'c>(
|
||||
) -> Result<sqlx::Transaction<'c, sqlx::Postgres>> {
|
||||
use FlowModuleValue::*;
|
||||
for module in &mut *modules {
|
||||
let mut val = serde_json::from_str::<FlowModuleValue>(module.value.get())
|
||||
.map_err(|err| Error::InternalErr(format!("reduce: Failed to parse flow module value: {}", err)))?;
|
||||
let mut val =
|
||||
serde_json::from_str::<FlowModuleValue>(module.value.get()).map_err(|err| {
|
||||
Error::InternalErr(format!(
|
||||
"reduce: Failed to parse flow module value: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
match &mut val {
|
||||
RawScript { .. } => {
|
||||
// In order to avoid an unnecessary `.clone()` of `val`, take ownership of it's content
|
||||
@@ -1107,9 +1128,14 @@ async fn reduce<'c>(
|
||||
concurrency_time_window_s,
|
||||
is_trigger,
|
||||
..
|
||||
} = std::mem::replace(&mut val, Identity) else { unreachable!() };
|
||||
} = std::mem::replace(&mut val, Identity)
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let id;
|
||||
(tx, id) = insert_flow_node(tx, path, workspace_id, Some(&content), lock.as_ref(), None).await?;
|
||||
(tx, id) =
|
||||
insert_flow_node(tx, path, workspace_id, Some(&content), lock.as_ref(), None)
|
||||
.await?;
|
||||
val = FlowScript {
|
||||
input_transforms,
|
||||
id,
|
||||
@@ -1120,32 +1146,56 @@ async fn reduce<'c>(
|
||||
concurrency_time_window_s,
|
||||
is_trigger,
|
||||
};
|
||||
},
|
||||
}
|
||||
ForloopFlow { modules, modules_node, .. }
|
||||
| WhileloopFlow { modules, modules_node, .. } => {
|
||||
| WhileloopFlow { modules, modules_node, .. } => {
|
||||
tx = insert_flow_modules(
|
||||
tx, path, workspace_id, failure_module, same_worker,
|
||||
modules, modules_node
|
||||
).await?;
|
||||
tx,
|
||||
path,
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
modules,
|
||||
modules_node,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BranchOne { branches, default, default_node, .. } => {
|
||||
for branch in branches.iter_mut() {
|
||||
tx = insert_flow_modules(
|
||||
tx, path, workspace_id, failure_module, same_worker,
|
||||
&mut branch.modules, &mut branch.modules_node
|
||||
).await?;
|
||||
tx,
|
||||
path,
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
&mut branch.modules,
|
||||
&mut branch.modules_node,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
tx = insert_flow_modules(
|
||||
tx, path, workspace_id, failure_module, same_worker,
|
||||
default, default_node
|
||||
).await?;
|
||||
tx,
|
||||
path,
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
default,
|
||||
default_node,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
BranchAll { branches, .. } => {
|
||||
for branch in branches.iter_mut() {
|
||||
tx = insert_flow_modules(
|
||||
tx, path, workspace_id, failure_module, same_worker,
|
||||
&mut branch.modules, &mut branch.modules_node
|
||||
).await?;
|
||||
tx,
|
||||
path,
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
&mut branch.modules,
|
||||
&mut branch.modules_node,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
+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.434.1";
|
||||
export const VERSION = "v1.435.2";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.434.1";
|
||||
export const VERSION = "1.435.2";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -129,12 +129,6 @@ services:
|
||||
- PORT=8001
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- MODE=indexer
|
||||
- TANTIVY_MAX_INDEXED_JOB_LOG_SIZE__MB=1 # job logs bigger than this will be truncated before indexing
|
||||
- TANTIVY_S3_BACKUP_PERIOD__S=3600 # how often to backup the index into object storage
|
||||
- TANTIVY_INDEX_WRITER_MEMORY_BUDGET__MB=100 # higher budget for higher indexing throughput
|
||||
- TANTIVY_REFRESH_INDEX_PERIOD__S=300 #how often to start indexing new jobs
|
||||
- TANTIVY_DOC_COMMIT_MAX_BATCH_SIZE=100000 #how many documents to batch in one commit
|
||||
- TANTIVY_SHOW_MEMORY_EVERY=10000 #log memory usage and progress every so many documents indexed
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
Generated
+68
-64
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.434.1",
|
||||
"version": "1.435.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.434.1",
|
||||
"version": "1.435.2",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.32.1",
|
||||
@@ -87,10 +87,10 @@
|
||||
"@hey-api/openapi-ts": "^0.43.0",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@rgossiaux/svelte-headlessui": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/package": "^2.2.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/d3": "^7.4.0",
|
||||
@@ -130,7 +130,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tslib": "^2.6.1",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^5",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-circular-dependency": "^0.2.1",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"yootils": "^0.3.1"
|
||||
@@ -3584,9 +3584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
@@ -3897,10 +3897,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
"version": "1.0.0-next.24",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz",
|
||||
"integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==",
|
||||
"dev": true
|
||||
"version": "1.0.0-next.28",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
|
||||
"integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
@@ -4214,32 +4215,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/adapter-static": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
|
||||
"integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz",
|
||||
"integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.0.tgz",
|
||||
"integrity": "sha512-1uyXvzC2Lu1FZa30T4y5jUAC21R309ZMRG0TPt+PPPbNUoDpy8zSmSNVWYaBWxYDqLGQ5oPNWvjvvF2IjJ1jmA==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz",
|
||||
"integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^0.6.0",
|
||||
"devalue": "^4.3.2",
|
||||
"esm-env": "^1.0.0",
|
||||
"import-meta-resolve": "^4.0.0",
|
||||
"devalue": "^5.1.0",
|
||||
"esm-env": "^1.2.1",
|
||||
"import-meta-resolve": "^4.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"magic-string": "^0.30.5",
|
||||
"mrmime": "^2.0.0",
|
||||
"sade": "^1.8.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"sirv": "^2.0.4",
|
||||
"sirv": "^3.0.0",
|
||||
"tiny-glob": "^0.2.9"
|
||||
},
|
||||
"bin": {
|
||||
@@ -4249,9 +4251,9 @@
|
||||
"node": ">=18.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0",
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||
"vite": "^5.0.3"
|
||||
"vite": "^5.0.3 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/package": {
|
||||
@@ -4277,17 +4279,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz",
|
||||
"integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
|
||||
"integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte-inspector": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"deepmerge": "^4.3.1",
|
||||
"kleur": "^4.1.5",
|
||||
"magic-string": "^0.30.5",
|
||||
"svelte-hmr": "^0.15.3",
|
||||
"magic-string": "^0.30.10",
|
||||
"svelte-hmr": "^0.16.0",
|
||||
"vitefu": "^0.2.5"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4299,9 +4301,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte-inspector": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz",
|
||||
"integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
|
||||
"integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
@@ -6434,10 +6436,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz",
|
||||
"integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==",
|
||||
"dev": true
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
"version": "1.1.0",
|
||||
@@ -6990,9 +6993,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esm-env": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
|
||||
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
|
||||
"integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esm-env-robust": {
|
||||
"version": "0.0.3",
|
||||
@@ -7932,10 +7936,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
|
||||
"integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
||||
"integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
@@ -8608,14 +8613,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.5",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
||||
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
|
||||
"version": "0.30.14",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
|
||||
"integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/map-obj": {
|
||||
@@ -9781,6 +9783,7 @@
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
||||
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -11960,17 +11963,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
|
||||
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
|
||||
"integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@polka/url": "^1.0.0-next.24",
|
||||
"mrmime": "^2.0.0",
|
||||
"totalist": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
@@ -12642,9 +12646,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-hmr": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
|
||||
"integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==",
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
|
||||
"integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20 || ^14.13.1 || >= 16"
|
||||
@@ -13064,6 +13068,7 @@
|
||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
||||
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -13395,15 +13400,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz",
|
||||
"integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==",
|
||||
"version": "5.4.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
||||
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.40",
|
||||
"rollup": "^4.13.0"
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.434.1",
|
||||
"version": "1.435.2",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -21,10 +21,10 @@
|
||||
"@hey-api/openapi-ts": "^0.43.0",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@rgossiaux/svelte-headlessui": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/package": "^2.2.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/d3": "^7.4.0",
|
||||
@@ -64,7 +64,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tslib": "^2.6.1",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^5",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-circular-dependency": "^0.2.1",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"yootils": "^0.3.1"
|
||||
|
||||
+55
-53
@@ -27,61 +27,63 @@
|
||||
</head>
|
||||
<body data-sveltekit-preload-code="viewport" class="outline-none focus:outline-none">
|
||||
<div style="display: contents">
|
||||
<div id="mainbg" style="position: absolute; bottom: 0; left: 0; width: 100%; height: 100vh">
|
||||
</div>
|
||||
<div id="svelte-global-loader">
|
||||
<div id="mainbg" style="position: absolute; bottom: 0; left: 0; width: 100%; height: 100vh">
|
||||
</div>
|
||||
|
||||
<div style="position: fixed; left: 50%; top: 40%; transform: translate(-50%, -50%)">
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Windmill"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="128px"
|
||||
height="128px"
|
||||
viewBox="0 0 256 256"
|
||||
style="enable-background: new 0 0 256 256"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
|
||||
<div style="position: fixed; left: 50%; top: 40%; transform: translate(-50%, -50%)">
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Windmill"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="128px"
|
||||
height="128px"
|
||||
viewBox="0 0 256 256"
|
||||
style="enable-background: new 0 0 256 256"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
|
||||
"
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "
|
||||
/>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "
|
||||
/>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<div
|
||||
id="loading-text"
|
||||
style="
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
color: gray;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
"
|
||||
>
|
||||
Loading...
|
||||
/>
|
||||
<polygon
|
||||
class="st3"
|
||||
points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "
|
||||
/>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "
|
||||
/>
|
||||
<polygon
|
||||
class="st2"
|
||||
points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<div
|
||||
id="loading-text"
|
||||
style="
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
color: gray;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
"
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
%sveltekit.body%
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
</Label>
|
||||
|
||||
{#if !disabled}
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex flex-row gap-2 mb-2">
|
||||
<CronBuilder let:close>
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let link: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="text-xs text-tertiary font-normal">
|
||||
<slot />
|
||||
{#if link}
|
||||
<a href={link} target="_blank">Learn more</a>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -214,6 +214,7 @@
|
||||
export let small = false
|
||||
export let scriptLang: Preview['language'] | 'bunnative'
|
||||
export let disabled: boolean = false
|
||||
export let lineNumbersMinChars = 3
|
||||
|
||||
const rHash = randomHash()
|
||||
$: filePath = computePath(path)
|
||||
@@ -1131,6 +1132,7 @@
|
||||
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
model,
|
||||
fontSize: !small ? 14 : 12,
|
||||
lineNumbersMinChars,
|
||||
// overflowWidgetsDomNode: widgets,
|
||||
tabSize: lang == 'python' ? 4 : 2,
|
||||
folding
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
>
|
||||
<FlowGraphV2
|
||||
{triggerNode}
|
||||
earlyStop={flow.value.skip_expr !== undefined}
|
||||
cache={flow.value.cache_ttl !== undefined}
|
||||
path={flow?.path}
|
||||
{download}
|
||||
minHeight={400}
|
||||
|
||||
@@ -1244,6 +1244,8 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
earlyStop={job.raw_flow?.skip_expr !== undefined}
|
||||
cache={job.raw_flow?.cache_ttl !== undefined}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
preprocessorModule={job.raw_flow?.preprocessor_module}
|
||||
|
||||
@@ -325,7 +325,7 @@
|
||||
<div class="text-secondary pb-4 text-xs"
|
||||
>Setting SMTP unlocks sending emails upon adding new users to the workspace or the
|
||||
instance or sending critical alerts.
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/misc/setup_smtp">Learn more</a
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/advanced/instance_settings#smtp">Learn more</a
|
||||
></div
|
||||
>
|
||||
{:else if category == "Indexer/Search"}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { loadSchedules, saveSchedule } from './flows/scheduleUtils'
|
||||
import { type Writable, writable } from 'svelte/store'
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
|
||||
export let schema: any
|
||||
export let isFlow: boolean
|
||||
@@ -48,14 +49,18 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScheduleEditor
|
||||
on:update={() => {
|
||||
updateSchedules(true)
|
||||
}}
|
||||
bind:this={scheduleEditor}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<Description link="https://www.windmill.dev/docs/core_concepts/scheduling">
|
||||
Run scripts and flows automatically on a recurring basis using cron expressions. Each script or
|
||||
flow can have multiple schedules, with one designated as primary.
|
||||
</Description>
|
||||
<ScheduleEditor
|
||||
on:update={() => {
|
||||
updateSchedules(true)
|
||||
}}
|
||||
bind:this={scheduleEditor}
|
||||
/>
|
||||
|
||||
{#if $primarySchedule == undefined}
|
||||
<Skeleton layout={[[12]]} />
|
||||
{:else if $primarySchedule}
|
||||
@@ -163,7 +168,7 @@
|
||||
}}
|
||||
variant="contained"
|
||||
color="dark"
|
||||
size="md"
|
||||
size="sm"
|
||||
startIcon={{ icon: Calendar }}
|
||||
>
|
||||
Set Primary Schedule
|
||||
|
||||
@@ -648,7 +648,7 @@
|
||||
<Tab value="triggers">
|
||||
Triggers
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/getting_started/trigger_scripts"
|
||||
documentationLink="https://www.windmill.dev/docs/getting_started/triggers"
|
||||
>
|
||||
Configure how this script will be triggered.
|
||||
</Tooltip>
|
||||
|
||||
@@ -317,9 +317,10 @@
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={60} minSize={10} class="!overflow-visible">
|
||||
<div class="pl-2 h-full !overflow-visible bg-gray-50 dark:bg-[#272D38]">
|
||||
<div class="h-full !overflow-visible bg-gray-50 dark:bg-[#272D38]">
|
||||
{#key lang}
|
||||
<Editor
|
||||
lineNumbersMinChars={4}
|
||||
folding
|
||||
{path}
|
||||
bind:code
|
||||
|
||||
@@ -48,9 +48,7 @@
|
||||
{/if}
|
||||
</h2>
|
||||
<slot name="action" />
|
||||
{#if collapsable && collapsed}
|
||||
<slot name="badge" />
|
||||
{/if}
|
||||
<slot name="badge" />
|
||||
</div>
|
||||
<div class={collapsable && collapsed ? `hidden ${$$props.class}` : `${$$props.class}`}>
|
||||
<slot />
|
||||
|
||||
@@ -225,6 +225,8 @@
|
||||
editor = meditor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
model,
|
||||
lineDecorationsWidth: 6,
|
||||
lineNumbersMinChars: 2,
|
||||
// overflowWidgetsDomNode: widgets,
|
||||
fontSize: small ? 12 : 14
|
||||
})
|
||||
|
||||
@@ -450,10 +450,11 @@
|
||||
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
model,
|
||||
// overflowWidgetsDomNode: widgets,
|
||||
lineNumbers: 'off',
|
||||
// lineNumbers: 'on',
|
||||
lineDecorationsWidth: 6,
|
||||
lineNumbersMinChars: 2,
|
||||
fontSize,
|
||||
suggestOnTriggerCharacters: true,
|
||||
lineDecorationsWidth: 14
|
||||
suggestOnTriggerCharacters: true
|
||||
})
|
||||
|
||||
editor.onDidFocusEditorText(() => {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<label
|
||||
for={id}
|
||||
class="{$$props.class || ''} z-auto inline-flex items-center duration-50 {disabled
|
||||
class="{$$props.class || ''} z-auto flex flex-row items-center duration-50 {disabled
|
||||
? 'grayscale opacity-50'
|
||||
: 'cursor-pointer'}"
|
||||
>
|
||||
@@ -104,9 +104,7 @@
|
||||
<slot name="right" />
|
||||
</label>
|
||||
{#if eeOnly && disabled}
|
||||
<span
|
||||
class="inline-flex text-xs text-primary items-center gap-1 !text-yellow-500 whitespace-nowrap ml-8"
|
||||
>
|
||||
<span class="inline-flex text-xs items-center gap-1 !text-yellow-500 whitespace-nowrap ml-8">
|
||||
<AlertTriangle size={16} />
|
||||
EE only <Tooltip>Enterprise Edition only feature</Tooltip>
|
||||
</span>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
summary: string
|
||||
policy: any
|
||||
draft_only?: boolean
|
||||
custom_path?: string
|
||||
}
|
||||
| undefined = undefined
|
||||
export let version: number | undefined = undefined
|
||||
|
||||
@@ -11,12 +11,11 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { AppService, DraftService, type Job, type Policy } from '$lib/gen'
|
||||
import { redo, undo } from '$lib/history'
|
||||
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
AlignHorizontalSpaceAround,
|
||||
BellOff,
|
||||
Bug,
|
||||
Clipboard,
|
||||
DiffIcon,
|
||||
Expand,
|
||||
FileJson,
|
||||
@@ -39,7 +38,6 @@
|
||||
import {
|
||||
classNames,
|
||||
cleanValueProperties,
|
||||
copyToClipboard,
|
||||
truncateRev,
|
||||
orderedJsonStringify,
|
||||
type Value,
|
||||
@@ -90,6 +88,9 @@
|
||||
import HideButton from './settingsPanel/HideButton.svelte'
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
import { computeS3FileInputPolicy, computeWorkspaceS3FileInputPolicy } from './appUtilsS3'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { base } from '$lib/base'
|
||||
import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte'
|
||||
|
||||
async function hash(message) {
|
||||
try {
|
||||
@@ -119,6 +120,7 @@
|
||||
summary: string
|
||||
policy: any
|
||||
draft_only?: boolean
|
||||
custom_path?: string
|
||||
}
|
||||
| undefined = undefined
|
||||
export let version: number | undefined = undefined
|
||||
@@ -479,14 +481,16 @@
|
||||
summary: $summary,
|
||||
policy,
|
||||
path: npath,
|
||||
deployment_message: deploymentMsg
|
||||
deployment_message: deploymentMsg,
|
||||
custom_path: $userStore?.is_admin || $userStore?.is_super_admin ? customPath : undefined
|
||||
}
|
||||
})
|
||||
savedApp = {
|
||||
summary: $summary,
|
||||
value: structuredClone($app),
|
||||
path: npath,
|
||||
policy
|
||||
policy,
|
||||
custom_path: customPath
|
||||
}
|
||||
const appHistory = await AppService.getAppHistoryByPath({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -885,6 +889,37 @@
|
||||
|
||||
let priorDarkMode = document.documentElement.classList.contains('dark')
|
||||
setTheme($app?.darkMode)
|
||||
|
||||
let customPath = savedApp?.custom_path
|
||||
let dirtyCustomPath = false
|
||||
let customPathError = ''
|
||||
$: fullCustomUrl = `${window.location.origin}${base}/a/${
|
||||
isCloudHosted() ? $workspaceStore + '/' : ''
|
||||
}${customPath}`
|
||||
async function appExists(customPath: string) {
|
||||
return await AppService.customPathExists({
|
||||
workspace: $workspaceStore!,
|
||||
customPath
|
||||
})
|
||||
}
|
||||
let validateTimeout: NodeJS.Timeout | undefined = undefined
|
||||
async function validateCustomPath(customPath: string): Promise<void> {
|
||||
customPathError = ''
|
||||
if (validateTimeout) {
|
||||
clearTimeout(validateTimeout)
|
||||
}
|
||||
validateTimeout = setTimeout(async () => {
|
||||
if (!/^[\w-]+(\/[\w-]+)*$/.test(customPath)) {
|
||||
customPathError = 'Invalid path'
|
||||
} else if (customPath !== savedApp?.custom_path && (await appExists(customPath))) {
|
||||
customPathError = 'Path already taken'
|
||||
} else {
|
||||
customPathError = ''
|
||||
}
|
||||
validateTimeout = undefined
|
||||
}, 500)
|
||||
}
|
||||
$: customPath !== undefined && validateCustomPath(customPath)
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
@@ -1071,7 +1106,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={{ icon: Save }}
|
||||
disabled={pathError != ''}
|
||||
disabled={pathError != '' || customPathError != ''}
|
||||
on:click={() => {
|
||||
if ($appPath == '') {
|
||||
createApp(newEditedPath)
|
||||
@@ -1121,29 +1156,68 @@
|
||||
</div>
|
||||
|
||||
<div class="my-6 box">
|
||||
Public url:
|
||||
<div class="text-secondary">
|
||||
<div>Public URL</div>
|
||||
</div>
|
||||
{#if secretUrl}
|
||||
{@const url = `${window.location.hostname}/public/${$workspaceStore}/${secretUrl}`}
|
||||
{@const href = window.location.protocol + '//' + url}
|
||||
<a
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
copyToClipboard(href)
|
||||
}}
|
||||
{href}
|
||||
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1 inline-flex gap-2"
|
||||
>
|
||||
{url}
|
||||
<span class="text-gray-700 ml-2">
|
||||
<Clipboard />
|
||||
</span>
|
||||
</a>
|
||||
{@const href = `${window.location.origin}${base}/public/${$workspaceStore}/${secretUrl}`}
|
||||
<ClipboardPanel content={href} size="md" />
|
||||
{:else}<Loader2 class="animate-spin" />
|
||||
{/if}
|
||||
<div class="text-xs text-secondary"
|
||||
>Share this url directly or embed it using an iframe (if requiring login, top-level domain
|
||||
of embedding app must be the same as the one of Windmill)</div
|
||||
>
|
||||
<div class="text-xs text-secondary mt-1">
|
||||
Share this url directly or embed it using an iframe (if requiring login, top-level domain
|
||||
of embedding app must be the same as the one of Windmill)
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert title="EE Only" type="warning" size="xs">
|
||||
Custom path is an enterprise only feature.
|
||||
</Alert>
|
||||
<div class="mb-2" />
|
||||
{:else if !($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
<Alert type="warning" title="Admin only" size="xs">
|
||||
Custom path can only be set by workspace admins
|
||||
</Alert>
|
||||
<div class="mb-2" />
|
||||
{/if}
|
||||
<Toggle
|
||||
on:change={({ detail }) => {
|
||||
customPath = detail ? '' : undefined
|
||||
}}
|
||||
checked={customPath !== undefined}
|
||||
options={{
|
||||
right: 'Use a custom URL'
|
||||
}}
|
||||
disabled={!$enterpriseLicense || !($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
/>
|
||||
|
||||
{#if customPath !== undefined}
|
||||
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
|
||||
<div>Custom path</div>
|
||||
</div>
|
||||
<input
|
||||
disabled={!($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
bind:value={customPath}
|
||||
class={customPathError === ''
|
||||
? ''
|
||||
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
|
||||
on:input={() => {
|
||||
dirtyCustomPath = true
|
||||
}}
|
||||
/>
|
||||
<div class="text-secondary text-sm flex items-center gap-1 mt-2 w-full justify-between">
|
||||
<div>Custom public URL</div>
|
||||
</div>
|
||||
<ClipboardPanel content={fullCustomUrl} size="md" />
|
||||
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5"
|
||||
>{dirtyCustomPath ? customPathError : ''}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Alert type="info" title="Only latest deployed app is publicly available">
|
||||
You will still need to deploy the app to make visible the latest changes
|
||||
|
||||
+3
-2
@@ -133,8 +133,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
class="border bg-surface absolute top-0.5 right-2 p-0.5"
|
||||
on:click={() => (fullscreen = true)}><Maximize2 size={12} /></button
|
||||
title="Open in drawer"
|
||||
class="border bg-surface absolute hover:text-primary top-0 right-2 p-0.5 text-secondary"
|
||||
on:click={() => (fullscreen = true)}><Maximize2 size={10} /></button
|
||||
>
|
||||
{#if focus}
|
||||
<div class="relative w-full">
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let notificationCount = 0
|
||||
export let notificationLimit: number | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if notificationCount > 0}
|
||||
<div
|
||||
class="bg-red-500 text-white text-[0.6rem] rounded-md w-5 h-5 flex items-center justify-center"
|
||||
>
|
||||
{#if notificationLimit && notificationCount > notificationLimit}
|
||||
{`${notificationLimit}+`}
|
||||
{:else}
|
||||
{notificationCount}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { RefreshCw } from 'lucide-svelte'
|
||||
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
|
||||
export let loading: boolean
|
||||
|
||||
let buttonHover = false
|
||||
</script>
|
||||
|
||||
<Popover>
|
||||
<Button
|
||||
on:mouseenter={() => (buttonHover = true)}
|
||||
on:mouseleave={() => (buttonHover = false)}
|
||||
color="light"
|
||||
size="xs2"
|
||||
variant="border"
|
||||
on:click
|
||||
>
|
||||
<RefreshCw class={loading ? 'animate-spin ' : ''} size="14" />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
{#if loading}
|
||||
{#if buttonHover}
|
||||
Stop Refreshing
|
||||
{:else}
|
||||
Refreshing...
|
||||
{/if}
|
||||
{:else}
|
||||
Refresh
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
export let horizontal: boolean = false
|
||||
export let gap: 'none' | 'sm' | 'md' | 'lg' = 'sm'
|
||||
export let justify: 'start' | 'center' | 'end' | 'between' = 'start'
|
||||
export let wFull = true
|
||||
|
||||
const gapMap = {
|
||||
none: '',
|
||||
sm: 'gap-2',
|
||||
md: 'gap-4',
|
||||
lg: 'gap-8'
|
||||
}
|
||||
|
||||
const justifyMap = {
|
||||
start: 'justify-start',
|
||||
center: 'justify-center',
|
||||
end: 'justify-end',
|
||||
between: 'justify-between'
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if horizontal}
|
||||
<div
|
||||
class="flex flex-row h-full {wFull ? 'w-full' : ''} {gapMap[gap]} items-center {justifyMap[
|
||||
justify
|
||||
]}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex flex-col h-full {wFull ? 'w-full' : ''} {gapMap[gap]} items-center {justifyMap[
|
||||
justify
|
||||
]}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="w-full flex-grow bg-blue-300">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -70,10 +70,12 @@
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="ml-4 text-left flex-1">
|
||||
<h3 class="text-lg font-medium text-primary">
|
||||
{title}
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-tertiary">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h3>{title}</h3>
|
||||
<slot name="settings" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-sm text-tertiary">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import Portal from '$lib/components/Portal.svelte'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import { X } from 'lucide-svelte'
|
||||
import List from '$lib/components/common/layout/List.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
export let title: string
|
||||
|
||||
export let css: any = {}
|
||||
export let target: string = ''
|
||||
export let isOpen = false
|
||||
export let fixedSize: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' = 'md'
|
||||
|
||||
// Add size mapping with custom pixel values
|
||||
const sizeStyles = {
|
||||
xs: { width: '400px', height: '250px' },
|
||||
sm: { width: '600px', height: '400px' },
|
||||
md: { width: '800px', height: '500px' },
|
||||
lg: { width: '1400px', height: '720px' },
|
||||
xl: { width: '1600px', height: '800px' },
|
||||
xxl: { width: '1600px', height: '1000px' }
|
||||
}
|
||||
|
||||
export function close() {
|
||||
isOpen = false
|
||||
}
|
||||
|
||||
export function open() {
|
||||
isOpen = true
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function fadeFast(node: HTMLElement) {
|
||||
return fade(node, { duration: 200 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
{#if isOpen}
|
||||
<Portal name="always-mounted" {target}>
|
||||
<div
|
||||
class={'fixed top-0 bottom-0 left-0 right-0 transition-all overflow-auto z-[1100] bg-black bg-opacity-60 w-full h-full'}
|
||||
transition:fadeFast|local
|
||||
>
|
||||
<div class="flex min-h-full items-center justify-center p-8">
|
||||
<div
|
||||
style={`width: ${sizeStyles[fixedSize].width}; height: ${sizeStyles[fixedSize].height}; ${
|
||||
css?.popup?.style || ''
|
||||
}`}
|
||||
class={twMerge(
|
||||
'max-h-screen-80 max-w-screen-80 rounded-lg relative bg-surface pt-2 px-4 pb-4',
|
||||
css?.popup?.class,
|
||||
'wm-modal-form-popup'
|
||||
)}
|
||||
use:clickOutside
|
||||
on:click_outside={() => {
|
||||
close()
|
||||
}}
|
||||
>
|
||||
<List gap="md">
|
||||
<div class="flex w-full">
|
||||
<List horizontal justify="between">
|
||||
<h3>{title}</h3>
|
||||
<div class="grow w-min-0">
|
||||
<List horizontal justify="between">
|
||||
<div class="min-w-0 grow">
|
||||
<slot name="header-left" />
|
||||
</div>
|
||||
<div class="min-w-0 grow-0 justify-end">
|
||||
<List horizontal justify="end">
|
||||
<slot name="header-right" />
|
||||
<div class="w-8">
|
||||
<button
|
||||
on:click={() => {
|
||||
isOpen = false
|
||||
}}
|
||||
class="hover:bg-surface-hover rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<X class="text-tertiary " />
|
||||
</button>
|
||||
</div>
|
||||
</List>
|
||||
</div>
|
||||
</List>
|
||||
</div>
|
||||
</List>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="w-full flex grow min-h-0" on:click|stopPropagation={() => {}}>
|
||||
<slot />
|
||||
</div>
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
{/if}
|
||||
@@ -17,6 +17,7 @@
|
||||
export let target: string | HTMLElement | undefined = undefined
|
||||
export let noTransition = false
|
||||
export let popupHover = false
|
||||
export let preventPopupClosingOnClickInside = false
|
||||
</script>
|
||||
|
||||
<Popover on:close class="leading-none">
|
||||
@@ -28,6 +29,7 @@
|
||||
<ConditionalPortal condition={shouldUsePortal} {target}>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
use:floatingContent
|
||||
class={`z5000 ${floatingClasses}`}
|
||||
@@ -37,6 +39,7 @@
|
||||
on:mouseleave={() => {
|
||||
popupHover = false
|
||||
}}
|
||||
on:click={(e) => preventPopupClosingOnClickInside && e.stopPropagation()}
|
||||
>
|
||||
{#if !noTransition}
|
||||
<Transition
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils'
|
||||
import { Clipboard } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let content: string
|
||||
export let title: string | undefined = undefined
|
||||
export let size: 'sm' | 'md' = 'sm'
|
||||
</script>
|
||||
|
||||
{#if title !== undefined}
|
||||
@@ -19,6 +21,8 @@
|
||||
copyToClipboard(content)
|
||||
}}
|
||||
>
|
||||
<div class="text-xs truncate whitespace-no-wrap grow">{content}</div>
|
||||
<div class={twMerge('truncate whitespace-no-wrap grow', size === 'sm' ? 'text-xs' : 'text-sm')}
|
||||
>{content}</div
|
||||
>
|
||||
<Clipboard size={12} class="flex-shrink-0" />
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import TriggerTokens from '../triggers/TriggerTokens.svelte'
|
||||
let userSettings: UserSettings
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
|
||||
export let token: string
|
||||
export let scopes: string[] = []
|
||||
@@ -79,6 +80,11 @@
|
||||
/>
|
||||
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<Description link="https://www.windmill.dev/docs/advanced/email_triggers">
|
||||
Email triggers execute scripts and flows when emails are sent to specific addresses. Each
|
||||
trigger has its own unique email address that can be used to invoke the script or flow.
|
||||
</Description>
|
||||
|
||||
{#if loading}
|
||||
<Skeleton layout={[[18]]} />
|
||||
{:else}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
By default, adding a trigger will set the schedule to 15 minutes. To see all ways to trigger
|
||||
a flow, check
|
||||
<a
|
||||
href="https://www.windmill.dev/docs/getting_started/trigger_flows"
|
||||
href="https://www.windmill.dev/docs/getting_started/triggers"
|
||||
target="_blank"
|
||||
class="text-blue-400">Triggering Flows</a
|
||||
>.
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
{#if !noEditor}
|
||||
{#key flowModule.id}
|
||||
<Editor
|
||||
|
||||
folding
|
||||
path={$pathStore + '/' + flowModule.id}
|
||||
bind:websocketAlive
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
right: 'Worker group tag (queue)',
|
||||
rightTooltip:
|
||||
"When a worker group tag is defined at the flow level, any steps inside the flow will run on any worker group that listen to that tag, regardless of the steps tag. If no worker group tags is defined, the flow controls will be executed with the default tag 'flow' and the steps will be executed with their respective tag",
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/worker_groups'
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/worker_groups'
|
||||
}}
|
||||
class="py-1"
|
||||
/>
|
||||
@@ -185,7 +185,8 @@
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs',
|
||||
rightTooltip: 'When enabled, the flow will cache the results of the flow for each possible set of inputs.',
|
||||
rightTooltip:
|
||||
'When enabled, the flow will cache the results of the flow for each possible set of inputs.',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/flows/cache#cache-flows'
|
||||
}}
|
||||
class="py-1"
|
||||
@@ -226,7 +227,8 @@
|
||||
rightTooltip:
|
||||
'If the inputs meet the predefined condition, the flow will not run.' +
|
||||
'to decide if the flow should stop early.',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/flows/early_stop#early-stop-for-flow'
|
||||
rightDocumentationLink:
|
||||
'https://www.windmill.dev/docs/flows/early_stop#early-stop-for-flow'
|
||||
}}
|
||||
class="py-1"
|
||||
/>
|
||||
@@ -316,7 +318,8 @@
|
||||
'Steps will share a folder at `./shared` in which they can store heavier data and ' +
|
||||
'pass them to the next step. Beware that the `./shared` folder is not ' +
|
||||
'preserved across suspends and sleeps.',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/persistent_storage/within_windmill#shared-directory'
|
||||
rightDocumentationLink:
|
||||
'https://www.windmill.dev/docs/core_concepts/persistent_storage/within_windmill#shared-directory'
|
||||
}}
|
||||
class="py-1"
|
||||
/>
|
||||
@@ -339,7 +342,8 @@
|
||||
right: 'Make runs invisible to others',
|
||||
rightTooltip:
|
||||
'When this option is enabled, manual executions of this script are invisible to users other than the user running it, including the owner(s). This setting can be overridden when this script is run manually from the advanced menu.',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/monitor_past_and_future_runs#invisible-runs'
|
||||
rightDocumentationLink:
|
||||
'https://www.windmill.dev/docs/core_concepts/monitor_past_and_future_runs#invisible-runs'
|
||||
}}
|
||||
class="py-1"
|
||||
/>
|
||||
@@ -365,27 +369,29 @@
|
||||
<!-- Concurrency Section -->
|
||||
{#if customUi?.settingsTabs?.concurrency != false}
|
||||
<div>
|
||||
<Toggle
|
||||
textClass="font-normal text-sm"
|
||||
color="nord"
|
||||
size="xs"
|
||||
disabled={!$enterpriseLicense}
|
||||
checked={Boolean($flowStore.value.concurrent_limit)}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.concurrent_limit) {
|
||||
$flowStore.value.concurrent_limit = undefined
|
||||
} else {
|
||||
$flowStore.value.concurrent_limit = 1
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Concurrency limits',
|
||||
rightTooltip: 'Allowed concurrency within a given timeframe',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/flows/concurrency_limit'
|
||||
}}
|
||||
class="py-1"
|
||||
eeOnly={true}
|
||||
/>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Toggle
|
||||
textClass="font-normal text-sm"
|
||||
color="nord"
|
||||
size="xs"
|
||||
disabled={!$enterpriseLicense}
|
||||
checked={Boolean($flowStore.value.concurrent_limit)}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.concurrent_limit) {
|
||||
$flowStore.value.concurrent_limit = undefined
|
||||
} else {
|
||||
$flowStore.value.concurrent_limit = 1
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Concurrency limits',
|
||||
rightTooltip: 'Allowed concurrency within a given timeframe',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/flows/concurrency_limit'
|
||||
}}
|
||||
class="py-1"
|
||||
eeOnly={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $flowStore.value.concurrent_limit}
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -483,28 +489,30 @@
|
||||
</Toggle>
|
||||
|
||||
<div>
|
||||
<Toggle
|
||||
textClass="font-normal text-sm"
|
||||
color="nord"
|
||||
size="xs"
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
checked={Boolean($flowStore.dedicated_worker)}
|
||||
on:change={() => {
|
||||
if ($flowStore.dedicated_worker) {
|
||||
$flowStore.dedicated_worker = undefined
|
||||
} else {
|
||||
$flowStore.dedicated_worker = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Flow is run on dedicated workers',
|
||||
rightTooltip:
|
||||
'When enabled, the flow will be executed on a dedicated worker.',
|
||||
rightDocumentationLink: 'https://www.windmill.dev/docs/core_concepts/jobs#high-priority-jobs'
|
||||
}}
|
||||
class="py-1"
|
||||
eeOnly={true}
|
||||
/>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Toggle
|
||||
textClass="font-normal text-sm"
|
||||
color="nord"
|
||||
size="xs"
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
checked={Boolean($flowStore.dedicated_worker)}
|
||||
on:change={() => {
|
||||
if ($flowStore.dedicated_worker) {
|
||||
$flowStore.dedicated_worker = undefined
|
||||
} else {
|
||||
$flowStore.dedicated_worker = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Flow is run on dedicated workers',
|
||||
rightTooltip: 'When enabled, the flow will be executed on a dedicated worker.',
|
||||
rightDocumentationLink:
|
||||
'https://www.windmill.dev/docs/core_concepts/jobs#high-priority-jobs'
|
||||
}}
|
||||
class="py-1"
|
||||
eeOnly={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $flowStore.dedicated_worker}
|
||||
<div>
|
||||
|
||||
@@ -330,6 +330,8 @@
|
||||
|
||||
<div class="z-10 flex-auto grow bg-surface-secondary" bind:clientHeight={minHeight}>
|
||||
<FlowGraphV2
|
||||
earlyStop={$flowStore.value?.skip_expr !== undefined}
|
||||
cache={$flowStore.value?.cache_ttl !== undefined}
|
||||
triggerNode={true}
|
||||
path={$pathStore}
|
||||
{newFlow}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import VirtualItemWrapper from './VirtualItemWrapper.svelte'
|
||||
import type { PropPickerContext } from '$lib/components/prop_picker'
|
||||
import FlowPropPicker from '$lib/components/flows/propPicker/FlowPropPicker.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Database, Square } from 'lucide-svelte'
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let bgColor: string = ''
|
||||
@@ -19,6 +22,8 @@
|
||||
export let inputJson: Object | undefined = undefined
|
||||
export let prefix = ''
|
||||
export let alwaysPluggable: boolean = false
|
||||
export let cache: boolean = false
|
||||
export let earlyStop: boolean = false
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||
@@ -62,7 +67,30 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="absolute text-sm right-12 -bottom-3 flex flex-row gap-1 z-10">
|
||||
{#if cache}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Database size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Cached</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if earlyStop}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<Square size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Early stop if condition met</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
{#if inputJson && $flowPropPickerConfig && (Object.keys(inputJson).length > 0 || alwaysPluggable)}
|
||||
<div class="absolute -bottom-[14px] right-[21px] translate-x-[50%] center-center">
|
||||
<FlowPropPicker json={inputJson} {prefix} />
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
export let newFlow: boolean = false
|
||||
|
||||
export let insertable = false
|
||||
export let earlyStop: boolean = false
|
||||
export let cache: boolean = false
|
||||
export let scroll = false
|
||||
export let moving: string | undefined = undefined
|
||||
|
||||
@@ -238,7 +240,9 @@
|
||||
flowModuleStates,
|
||||
selectedId: $selectedId,
|
||||
path,
|
||||
newFlow
|
||||
newFlow,
|
||||
cache,
|
||||
earlyStop
|
||||
},
|
||||
failureModule,
|
||||
preprocessorModule,
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
index: number
|
||||
disableAi: boolean
|
||||
disableMoveIds: string[]
|
||||
cache: boolean
|
||||
earlyStop: boolean
|
||||
}
|
||||
|
||||
const { selectedId } = getContext<{
|
||||
@@ -83,5 +85,7 @@
|
||||
inputJson={filteredInput}
|
||||
prefix="flow_input"
|
||||
alwaysPluggable
|
||||
cache={data.cache}
|
||||
earlyStop={data.earlyStop}
|
||||
/>
|
||||
</NodeWrapper>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import ContentSearchInner from '../ContentSearchInner.svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
import QuickMenuItem from '../search/QuickMenuItem.svelte'
|
||||
import { enterpriseLicense, superadmin, workspaceStore } from '$lib/stores'
|
||||
import { devopsRole, enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import uFuzzy from '@leeoniya/ufuzzy'
|
||||
import BarsStaggered from '../icons/BarsStaggered.svelte'
|
||||
import { scroll_into_view_if_needed_polyfill } from '../multiselect/utils'
|
||||
@@ -244,7 +244,7 @@
|
||||
queryParseErrors = searchResults.query_parse_errors
|
||||
indexMetadata = searchResults.index_metadata
|
||||
} catch (e) {
|
||||
sendUserToast(e, true)
|
||||
sendUserToast(e.body, true)
|
||||
}
|
||||
loadingCompletedRuns = false
|
||||
selectedItem = selectItem(0)
|
||||
@@ -610,7 +610,7 @@
|
||||
/>
|
||||
{:else if tab === 'logs'}
|
||||
<div class="p-2">
|
||||
{#if !$superadmin}
|
||||
{#if !$devopsRole}
|
||||
<Alert title="Service logs are only available to superadmins" type="warning">
|
||||
Service logs are only available to superadmins
|
||||
</Alert>
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import CriticalAlertModalInner from './CriticalAlertModalInner.svelte'
|
||||
import { SettingService } from '$lib/gen'
|
||||
import { SettingService, type CriticalAlert } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { workspaceStore, isCriticalAlertsUIOpen, devopsRole } from '$lib/stores'
|
||||
import Modal from '../common/modal/Modal.svelte'
|
||||
import {
|
||||
workspaceStore,
|
||||
isCriticalAlertsUIOpen,
|
||||
devopsRole,
|
||||
userStore,
|
||||
superadmin
|
||||
} from '$lib/stores'
|
||||
import Modal2 from '../common/modal/Modal2.svelte'
|
||||
import { Button, Popup } from '$lib/components/common'
|
||||
import List from '$lib/components/common/layout/List.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { BellOff, Bell, ExternalLink, Settings } from 'lucide-svelte'
|
||||
import { base } from '$lib/base'
|
||||
import Notification from '$lib/components/common/alert/Notification.svelte'
|
||||
|
||||
export let open: boolean = false
|
||||
export let numUnacknowledgedCriticalAlerts: number = 0
|
||||
export let muteSettings
|
||||
let workspaceContext = false
|
||||
let childRef
|
||||
|
||||
$: {
|
||||
setupApiFunctions(workspaceContext)
|
||||
@@ -68,15 +81,50 @@
|
||||
clearInterval(checkForNewAlertsInterval)
|
||||
})
|
||||
|
||||
async function saveWorkSpaceMuteSetting() {
|
||||
await SettingService.workspaceMuteCriticalAlertsUi({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
mute_critical_alerts: muteSettings.workspace
|
||||
}
|
||||
})
|
||||
sendUserToast(
|
||||
`Critical alert UI mute settings changed.\nPlease reload page for UI changes to take effect.`
|
||||
)
|
||||
childRef.refreshAlerts()
|
||||
}
|
||||
async function saveGlobalMuteSetting() {
|
||||
await SettingService.setGlobal({
|
||||
key: 'critical_alert_mute_ui',
|
||||
requestBody: { value: muteSettings.global }
|
||||
})
|
||||
sendUserToast(
|
||||
`Critical alert UI mute settings changed.\nPlease reload page for UI changes to take effect.`
|
||||
)
|
||||
childRef.refreshAlerts()
|
||||
}
|
||||
|
||||
async function updateHasUnacknowledgedCriticalAlerts(sendToast: boolean = false) {
|
||||
if (checkingForNewAlerts) return
|
||||
checkingForNewAlerts = true
|
||||
try {
|
||||
const unacknowledged = await getCriticalAlerts({
|
||||
const params = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 1000,
|
||||
acknowledged: false
|
||||
})
|
||||
}
|
||||
let unacknowledged: CriticalAlert[] = []
|
||||
if (!$devopsRole && $workspaceStore) {
|
||||
const res = await SettingService.workspaceGetCriticalAlerts({
|
||||
...params,
|
||||
workspace: $workspaceStore
|
||||
})
|
||||
unacknowledged = res.alerts ?? []
|
||||
} else {
|
||||
const res = await SettingService.getCriticalAlerts(params)
|
||||
unacknowledged = res.alerts ?? []
|
||||
}
|
||||
|
||||
if (
|
||||
numUnacknowledgedCriticalAlerts === 0 &&
|
||||
unacknowledged.length > 0 &&
|
||||
@@ -116,14 +164,123 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:open title="Critical Alerts" cancelText="Close" style="max-width: 66%;">
|
||||
<Modal2 bind:isOpen={open} title="Critical Alerts" target="#content" fixedSize="lg">
|
||||
<svelte:fragment slot="header-left">
|
||||
<Notification notificationCount={numUnacknowledgedCriticalAlerts} notificationLimit={9999} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="header-right">
|
||||
<List horizontal>
|
||||
{#if $superadmin || $userStore?.is_admin}
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
target="#mute-settings-button"
|
||||
preventPopupClosingOnClickInside={true}
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<div id="mute-settings-button">
|
||||
<Button variant="border" color="light" nonCaptureEvent>
|
||||
{#if muteSettings.global || muteSettings.workspace}
|
||||
<BellOff size="16" />
|
||||
{:else}
|
||||
<Bell size="16" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<List justify="start">
|
||||
<div class="w-full">
|
||||
{#if $superadmin}
|
||||
<Toggle
|
||||
on:change={saveGlobalMuteSetting}
|
||||
bind:checked={muteSettings.global}
|
||||
options={{
|
||||
right: 'Automatically acknowledge critical alerts instance wide'
|
||||
}}
|
||||
size="xs"
|
||||
stopPropagation={true}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<Toggle
|
||||
on:change={saveWorkSpaceMuteSetting}
|
||||
bind:checked={muteSettings.workspace}
|
||||
options={{
|
||||
right: 'Automatically acknowledge critical alerts for current workspace'
|
||||
}}
|
||||
size="xs"
|
||||
stopPropagation={true}
|
||||
/>
|
||||
</div>
|
||||
</List>
|
||||
</Popup>
|
||||
{/if}
|
||||
|
||||
{#if $superadmin}
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
target="#settings-button"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<div id="settings-button">
|
||||
<Button variant="border" color="light" nonCaptureEvent>
|
||||
<Settings size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<List justify="start" gap="none">
|
||||
<div class="w-full">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
href="{base}/?workspace=admins#superadmin-settings"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="w-full">
|
||||
<List horizontal justify="between" gap="sm">
|
||||
<div>Instance Critical Alert Settings</div>
|
||||
<ExternalLink size="16" />
|
||||
</List>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
href="{base}/workspace_settings?tab=error_handler"
|
||||
target="_blank"
|
||||
>
|
||||
Workspace Critical Alert Settings <ExternalLink size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
</List>
|
||||
</Popup>
|
||||
{:else}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
href="{base}/workspace_settings?tab=error_handler"
|
||||
target="_blank"
|
||||
>
|
||||
<List horizontal justify="between" gap="sm">
|
||||
<Settings size="16" />
|
||||
<ExternalLink size="16" />
|
||||
</List>
|
||||
</Button>
|
||||
{/if}
|
||||
</List>
|
||||
</svelte:fragment>
|
||||
|
||||
<CriticalAlertModalInner
|
||||
bind:workspaceContext
|
||||
{numUnacknowledgedCriticalAlerts}
|
||||
{updateHasUnacknowledgedCriticalAlerts}
|
||||
{getCriticalAlerts}
|
||||
{acknowledgeCriticalAlert}
|
||||
{acknowledgeAllCriticalAlerts}
|
||||
{muteSettings}
|
||||
bind:workspaceContext
|
||||
bind:this={childRef}
|
||||
/>
|
||||
</Modal>
|
||||
</Modal2>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user