diff --git a/CHANGELOG.md b/CHANGELOG.md index 111225c3a2..3fb83ac956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,137 @@ # Changelog +## [1.423.1](https://github.com/windmill-labs/windmill/compare/v1.423.0...v1.423.1) (2024-11-12) + + +### Bug Fixes + +* **autoscaling:** autoscaling thresholds to be >= and not > ([de00944](https://github.com/windmill-labs/windmill/commit/de00944f09699ff33a382f5a14f515ccf90b2454)) + +## [1.423.0](https://github.com/windmill-labs/windmill/compare/v1.422.1...v1.423.0) (2024-11-12) + + +### Features + +* s3 input available for public apps ([#4685](https://github.com/windmill-labs/windmill/issues/4685)) ([1671005](https://github.com/windmill-labs/windmill/commit/167100510032ab53cd609fb2c7629e67faceb093)) + + +### Bug Fixes + +* prevent underflow for autoscaling scalein ([5e9870a](https://github.com/windmill-labs/windmill/commit/5e9870a8a993032ec7d45c62e0023008da42c74a)) +* support multiple pip-extra-index-url with commas ([50861fc](https://github.com/windmill-labs/windmill/commit/50861fccc0cd86d4c76120c3306adf94f375330e)) + +## [1.422.1](https://github.com/windmill-labs/windmill/compare/v1.422.0...v1.422.1) (2024-11-12) + + +### Bug Fixes + +* fix password inputs ([e5e174a](https://github.com/windmill-labs/windmill/commit/e5e174ae9516f4c6b94ceb6e258b467f5c9a1f1a)) + +## [1.422.0](https://github.com/windmill-labs/windmill/compare/v1.421.2...v1.422.0) (2024-11-11) + + +### Features + +* expandable subflows in flows ([#4683](https://github.com/windmill-labs/windmill/issues/4683)) ([d44976f](https://github.com/windmill-labs/windmill/commit/d44976f35e45ade510d1ec220b5a1503e11f3db9)) +* **frontend:** critical alerts UI ([#4653](https://github.com/windmill-labs/windmill/issues/4653)) ([d9148ea](https://github.com/windmill-labs/windmill/commit/d9148eaa78680a93d81d71847a7df67e01f3c110)) + +## [1.421.2](https://github.com/windmill-labs/windmill/compare/v1.421.1...v1.421.2) (2024-11-08) + + +### Bug Fixes + +* **bash:** correctly propagate exit errors ([8bc9a02](https://github.com/windmill-labs/windmill/commit/8bc9a021a88391147c9170f56b5a0edddd55bc7d)) + +## [1.421.1](https://github.com/windmill-labs/windmill/compare/v1.421.0...v1.421.1) (2024-11-08) + + +### Bug Fixes + +* **python-client:** fix small break params of write_s3_file ([3ea12f1](https://github.com/windmill-labs/windmill/commit/3ea12f1821500e8b84549b892e3e1bb56a6ace4b)) + +## [1.421.0](https://github.com/windmill-labs/windmill/compare/v1.420.1...v1.421.0) (2024-11-07) + + +### Features + +* http custom routes for static assets ([#4666](https://github.com/windmill-labs/windmill/issues/4666)) ([dc8bd6d](https://github.com/windmill-labs/windmill/commit/dc8bd6d2b5d6f5cd8521f9034853175ec78d5639)) + + +### Bug Fixes + +* improve nested schema editor field change ([a3feca7](https://github.com/windmill-labs/windmill/commit/a3feca719799ea2bf08f2b49350e6b732a24abf4)) + +## [1.420.1](https://github.com/windmill-labs/windmill/compare/v1.420.0...v1.420.1) (2024-11-07) + + +### Bug Fixes + +* improve prop filtering on flow prop picker ([0501ad8](https://github.com/windmill-labs/windmill/commit/0501ad8a99f6f7d9fd26c996b754e8afe8f958b1)) + +## [1.420.0](https://github.com/windmill-labs/windmill/compare/v1.419.0...v1.420.0) (2024-11-07) + + +### Features + +* **frontend:** detect expr in flow input transform + filter right panel based on expr ([#4651](https://github.com/windmill-labs/windmill/issues/4651)) ([e9b7dca](https://github.com/windmill-labs/windmill/commit/e9b7dca20387e775fa50aaecd832890251582cf9)) +* **frontend:** nodes from flow can be connected directly in expr input through a plug icon ([#4652](https://github.com/windmill-labs/windmill/issues/4652)) ([ceaf56c](https://github.com/windmill-labs/windmill/commit/ceaf56c21ee2e548bc93859f9e0303e53b25b241)) + +## [1.419.0](https://github.com/windmill-labs/windmill/compare/v1.418.0...v1.419.0) (2024-11-06) + + +### Features + +* Add full-text search on windmill service logs ([#4576](https://github.com/windmill-labs/windmill/issues/4576)) ([77735d8](https://github.com/windmill-labs/windmill/commit/77735d859cfee9204f67a8e4f9885228d657a41d)) +* show path in flow script picker ([#4574](https://github.com/windmill-labs/windmill/issues/4574)) ([8e27392](https://github.com/windmill-labs/windmill/commit/8e27392afacbb725aaf9f9f892ab8a6171b59ce5)) +* websocket authentication ([#4635](https://github.com/windmill-labs/windmill/issues/4635)) ([4dda0fb](https://github.com/windmill-labs/windmill/commit/4dda0fb8cd8262ad3a2ab2b9d27e7043ac3bb891)) + + +### Bug Fixes + +* clarify error messages when job timeout or cancelled with more details ([771d740](https://github.com/windmill-labs/windmill/commit/771d740701902166f8b4e3f77aa9c5579237cb15)) +* **cli:** improve handling of deleted items on windows ([9a8dcc9](https://github.com/windmill-labs/windmill/commit/9a8dcc9a250caefa0b7c9523e1321599b7471c8b)) +* display logs in native mode when script fails ([#4655](https://github.com/windmill-labs/windmill/issues/4655)) ([7578ceb](https://github.com/windmill-labs/windmill/commit/7578cebaf92e729a26fd665e4b6f8357d34f59eb)) +* **frontend:** arg input json handling when the value is not of the same type as schema ([#4479](https://github.com/windmill-labs/windmill/issues/4479)) ([8d8156b](https://github.com/windmill-labs/windmill/commit/8d8156bd0773da3ddec81c46ad5fda114ecd3dda)) +* **frontend:** improve flow prop picker design ([323912c](https://github.com/windmill-labs/windmill/commit/323912c73c18d3bb6d136f2e6458389270364a0e)) + +## [1.418.0](https://github.com/windmill-labs/windmill/compare/v1.417.3...v1.418.0) (2024-11-04) + + +### Features + +* **frontend:** improve and simplify scheduled poll flows ([#4560](https://github.com/windmill-labs/windmill/issues/4560)) ([22ab51e](https://github.com/windmill-labs/windmill/commit/22ab51e9914376b47827f0be66708deef3a37767)) +* **oauth:** add snowflake oauth support ([#4622](https://github.com/windmill-labs/windmill/issues/4622)) ([693b7a4](https://github.com/windmill-labs/windmill/commit/693b7a4fd4da0e2b14cb6c8ea19537aa600225be)) + + +### Bug Fixes + +* **prometheus:** improve queue_count when tags have no more jobs ([09156b6](https://github.com/windmill-labs/windmill/commit/09156b65c2a0f0ac59fce1dede2b68b790323c49)) + +## [1.417.3](https://github.com/windmill-labs/windmill/compare/v1.417.2...v1.417.3) (2024-11-04) + + +### Bug Fixes + +* **cli:** improve pulling instance with folderPerInstance ([1e7909e](https://github.com/windmill-labs/windmill/commit/1e7909e95931e1e121cb3767bad093239254935d)) + +## [1.417.2](https://github.com/windmill-labs/windmill/compare/v1.417.1...v1.417.2) (2024-11-04) + + +### Bug Fixes + +* **cli:** improve pulling instance with folderPerInstance ([00748f4](https://github.com/windmill-labs/windmill/commit/00748f43feab4f28f0dd2682e3dc8ccc15a5f49f)) + +## [1.417.1](https://github.com/windmill-labs/windmill/compare/v1.417.0...v1.417.1) (2024-11-04) + + +### Bug Fixes + +* missing opts for pull and push instance configs ([#4630](https://github.com/windmill-labs/windmill/issues/4630)) ([e92a338](https://github.com/windmill-labs/windmill/commit/e92a338432d2732e6be74c3604b9fa95239e1ca3)) + +## 1.417.0 (2024-11-03) + +* feat(cli): instance yaml files inside the instance prefix folders (#4627) + ## [1.416.2](https://github.com/windmill-labs/windmill/compare/v1.416.1...v1.416.2) (2024-11-02) diff --git a/Dockerfile b/Dockerfile index 8683aa9738..cff245d3e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -175,9 +175,9 @@ RUN /usr/local/bin/python3 -m pip install pip-tools COPY --from=builder /frontend/build /static_frontend COPY --from=builder /windmill/target/release/windmill ${APP}/windmill -COPY --from=denoland/deno:2.0.2 --chmod=755 /usr/bin/deno /usr/bin/deno +COPY --from=denoland/deno:2.0.4 --chmod=755 /usr/bin/deno /usr/bin/deno -COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.34 /usr/local/bin/bun /usr/bin/bun COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer diff --git a/README.md b/README.md index eb6c64ee2a..e85d34111b 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,8 @@ you to have it being synced automatically everyday. See the [./frontend/README_DEV.md](./frontend/README_DEV.md) file for all running options. +Using [Nix](./frontend/README_DEV.md#nix). + ### only Frontend This will use the backend of but your own frontend with hot-code reloading. Note that you will need to use a username / password login due to CSRF checks using a different auth provider. diff --git a/backend/.sqlx/query-00ce4ed3ca0eac7cb6283b047353a64b9e78c4beb423f04baef9a53fbf87e9f9.json b/backend/.sqlx/query-00ce4ed3ca0eac7cb6283b047353a64b9e78c4beb423f04baef9a53fbf87e9f9.json deleted file mode 100644 index e4909fc4da..0000000000 --- a/backend/.sqlx/query-00ce4ed3ca0eac7cb6283b047353a64b9e78c4beb423f04baef9a53fbf87e9f9.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO alerts (alert_type, message) VALUES ('recovered_critical_error', $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "00ce4ed3ca0eac7cb6283b047353a64b9e78c4beb423f04baef9a53fbf87e9f9" -} diff --git a/backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json b/backend/.sqlx/query-0b94bd4c98a11ca1b7e5e34dd1ee6fcb0b7a54ed4218fa3cf23cc929d009d50f.json similarity index 50% rename from backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json rename to backend/.sqlx/query-0b94bd4c98a11ca1b7e5e34dd1ee6fcb0b7a54ed4218fa3cf23cc929d009d50f.json index eef46ce41d..75288e80ae 100644 --- a/backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json +++ b/backend/.sqlx/query-0b94bd4c98a11ca1b7e5e34dd1ee6fcb0b7a54ed4218fa3cf23cc929d009d50f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, edited_by = $6, email = $7, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE workspace_id = $8 AND path = $9", + "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE workspace_id = $10 AND path = $11", "describe": { "columns": [], "parameters": { @@ -10,6 +10,8 @@ "Varchar", "Bool", "JsonbArray", + "JsonbArray", + "Jsonb", "Varchar", "Varchar", "Text", @@ -18,5 +20,5 @@ }, "nullable": [] }, - "hash": "acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780" + "hash": "0b94bd4c98a11ca1b7e5e34dd1ee6fcb0b7a54ed4218fa3cf23cc929d009d50f" } diff --git a/backend/.sqlx/query-0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995.json b/backend/.sqlx/query-0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995.json new file mode 100644 index 0000000000..820e4f45ab --- /dev/null +++ b/backend/.sqlx/query-0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995.json @@ -0,0 +1,48 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n WHERE acknowledged = $1\n ORDER BY created_at DESC \n LIMIT $2 OFFSET $3", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "alert_type", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "message", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "acknowledged", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Bool", + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + true + ] + }, + "hash": "0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995" +} diff --git a/backend/.sqlx/query-fe4f91ca7e179e58041a5c0b1a36015175e9301c01a99e8f2b82cb853349a183.json b/backend/.sqlx/query-11b698f82a54aac68b3617047dfe2b18dd6da7d962118fee276af354218baac2.json similarity index 84% rename from backend/.sqlx/query-fe4f91ca7e179e58041a5c0b1a36015175e9301c01a99e8f2b82cb853349a183.json rename to backend/.sqlx/query-11b698f82a54aac68b3617047dfe2b18dd6da7d962118fee276af354218baac2.json index 65cb5c6633..9900461761 100644 --- a/backend/.sqlx/query-fe4f91ca7e179e58041a5c0b1a36015175e9301c01a99e8f2b82cb853349a183.json +++ b/backend/.sqlx/query-11b698f82a54aac68b3617047dfe2b18dd6da7d962118fee276af354218baac2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\" FROM http_trigger", + "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\" FROM http_trigger", "describe": { "columns": [ { @@ -65,6 +65,11 @@ } } } + }, + { + "ordinal": 10, + "name": "static_asset_config: _", + "type_info": "Jsonb" } ], "parameters": { @@ -80,8 +85,9 @@ false, false, false, - false + false, + true ] }, - "hash": "fe4f91ca7e179e58041a5c0b1a36015175e9301c01a99e8f2b82cb853349a183" + "hash": "11b698f82a54aac68b3617047dfe2b18dd6da7d962118fee276af354218baac2" } diff --git a/backend/.sqlx/query-c229744534f17f7622c3dee21bb1e7292ff17e6dffe58e80e53bff8baade07c8.json b/backend/.sqlx/query-333b484ffa030dee08e7b1161fcbc48af411377d2d9f58f92fc9d5eacdf0fba1.json similarity index 76% rename from backend/.sqlx/query-c229744534f17f7622c3dee21bb1e7292ff17e6dffe58e80e53bff8baade07c8.json rename to backend/.sqlx/query-333b484ffa030dee08e7b1161fcbc48af411377d2d9f58f92fc9d5eacdf0fba1.json index df94d893a6..bb21c2a9db 100644 --- a/backend/.sqlx/query-c229744534f17f7622c3dee21bb1e7292ff17e6dffe58e80e53bff8baade07c8.json +++ b/backend/.sqlx/query-333b484ffa030dee08e7b1161fcbc48af411377d2d9f58f92fc9d5eacdf0fba1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now())", + "query": "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, static_asset_config, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now())", "describe": { "columns": [], "parameters": { @@ -27,11 +27,12 @@ } } }, + "Jsonb", "Varchar", "Varchar" ] }, "nullable": [] }, - "hash": "c229744534f17f7622c3dee21bb1e7292ff17e6dffe58e80e53bff8baade07c8" + "hash": "333b484ffa030dee08e7b1161fcbc48af411377d2d9f58f92fc9d5eacdf0fba1" } diff --git a/backend/.sqlx/query-33cfb00f61dbae09467bd3732ce8e83c9835ccd30c90cb24788cdb54d1ceb575.json b/backend/.sqlx/query-33cfb00f61dbae09467bd3732ce8e83c9835ccd30c90cb24788cdb54d1ceb575.json new file mode 100644 index 0000000000..26e7a58854 --- /dev/null +++ b/backend/.sqlx/query-33cfb00f61dbae09467bd3732ce8e83c9835ccd30c90cb24788cdb54d1ceb575.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value::BOOLEAN as value FROM global_settings WHERE name = 'critical_alert_mute_ui'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "33cfb00f61dbae09467bd3732ce8e83c9835ccd30c90cb24788cdb54d1ceb575" +} diff --git a/backend/.sqlx/query-3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e.json b/backend/.sqlx/query-3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e.json new file mode 100644 index 0000000000..5a79a3d793 --- /dev/null +++ b/backend/.sqlx/query-3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e" +} diff --git a/backend/.sqlx/query-4d8640e84fccf1a0b799d8396a51e69345137e68d5096c70ba0a4332075d97ea.json b/backend/.sqlx/query-487d377e2df67fc3ea39d183ba9f99d45828d7c8e0ff10c5d74c454472e0493c.json similarity index 72% rename from backend/.sqlx/query-4d8640e84fccf1a0b799d8396a51e69345137e68d5096c70ba0a4332075d97ea.json rename to backend/.sqlx/query-487d377e2df67fc3ea39d183ba9f99d45828d7c8e0ff10c5d74c454472e0493c.json index b66edc6d73..7cd37b4c50 100644 --- a/backend/.sqlx/query-4d8640e84fccf1a0b799d8396a51e69345137e68d5096c70ba0a4332075d97ea.json +++ b/backend/.sqlx/query-487d377e2df67fc3ea39d183ba9f99d45828d7c8e0ff10c5d74c454472e0493c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE http_trigger \n SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, edited_by = $7, email = $8, is_async = $9, requires_auth = $10, edited_at = now() \n WHERE workspace_id = $11 AND path = $12", + "query": "UPDATE http_trigger \n SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, static_asset_config = $7, edited_by = $8, email = $9, is_async = $10, requires_auth = $11, edited_at = now() \n WHERE workspace_id = $12 AND path = $13", "describe": { "columns": [], "parameters": { @@ -24,6 +24,7 @@ } } }, + "Jsonb", "Varchar", "Varchar", "Bool", @@ -34,5 +35,5 @@ }, "nullable": [] }, - "hash": "4d8640e84fccf1a0b799d8396a51e69345137e68d5096c70ba0a4332075d97ea" + "hash": "487d377e2df67fc3ea39d183ba9f99d45828d7c8e0ff10c5d74c454472e0493c" } diff --git a/backend/.sqlx/query-4c0067c2135a259aea5cc2db60f7375a9a33671be8ef406427d90f67a98c9c9f.json b/backend/.sqlx/query-4c0067c2135a259aea5cc2db60f7375a9a33671be8ef406427d90f67a98c9c9f.json deleted file mode 100644 index 32295ea1e1..0000000000 --- a/backend/.sqlx/query-4c0067c2135a259aea5cc2db60f7375a9a33671be8ef406427d90f67a98c9c9f.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO alerts (alert_type, message) VALUES ('critical_error', $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "4c0067c2135a259aea5cc2db60f7375a9a33671be8ef406427d90f67a98c9c9f" -} diff --git a/backend/.sqlx/query-4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91.json b/backend/.sqlx/query-4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91.json new file mode 100644 index 0000000000..b57d834f87 --- /dev/null +++ b/backend/.sqlx/query-4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91" +} diff --git a/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json b/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json deleted file mode 100644 index 2f96d31ecb..0000000000 --- a/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now()) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "url", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "edited_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "server_id", - "type_info": "Varchar" - }, - { - "ordinal": 10, - "name": "last_server_ping", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "error", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "enabled", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "filters", - "type_info": "JsonbArray" - } - ], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Bool", - "Bool", - "JsonbArray", - "Varchar", - "Varchar" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - true, - true, - false, - false - ] - }, - "hash": "4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775" -} diff --git a/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json b/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json deleted file mode 100644 index a95b00b8c8..0000000000 --- a/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT *\n FROM websocket_trigger\n WHERE enabled IS TRUE AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "url", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "edited_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "server_id", - "type_info": "Varchar" - }, - { - "ordinal": 10, - "name": "last_server_ping", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "error", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "enabled", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "filters", - "type_info": "JsonbArray" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - true, - true, - false, - false - ] - }, - "hash": "5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7" -} diff --git a/backend/.sqlx/query-55d44f569f8ebfccddf975e1a330ef0dc286f4138efe923832371cdbac7157b0.json b/backend/.sqlx/query-7113d7cc72e44e4b7e01b69cc18cbe7b0399cf8ec0e9e6d2b05ceef589c432df.json similarity index 68% rename from backend/.sqlx/query-55d44f569f8ebfccddf975e1a330ef0dc286f4138efe923832371cdbac7157b0.json rename to backend/.sqlx/query-7113d7cc72e44e4b7e01b69cc18cbe7b0399cf8ec0e9e6d2b05ceef589c432df.json index ca0a922067..da3503cf35 100644 --- a/backend/.sqlx/query-55d44f569f8ebfccddf975e1a330ef0dc286f4138efe923832371cdbac7157b0.json +++ b/backend/.sqlx/query-7113d7cc72e44e4b7e01b69cc18cbe7b0399cf8ec0e9e6d2b05ceef589c432df.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, edited_by = $5, email = $6, is_async = $7, requires_auth = $8, edited_at = now() \n WHERE workspace_id = $9 AND path = $10", + "query": "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, static_asset_config = $5, edited_by = $6, email = $7, is_async = $8, requires_auth = $9, edited_at = now() \n WHERE workspace_id = $10 AND path = $11", "describe": { "columns": [], "parameters": { @@ -22,6 +22,7 @@ } } }, + "Jsonb", "Varchar", "Varchar", "Bool", @@ -32,5 +33,5 @@ }, "nullable": [] }, - "hash": "55d44f569f8ebfccddf975e1a330ef0dc286f4138efe923832371cdbac7157b0" + "hash": "7113d7cc72e44e4b7e01b69cc18cbe7b0399cf8ec0e9e6d2b05ceef589c432df" } diff --git a/backend/.sqlx/query-a69ba7471d1a5faa145bebbd17d43e6dbe02e9e92ebbc31a50c99c3a04719284.json b/backend/.sqlx/query-a69ba7471d1a5faa145bebbd17d43e6dbe02e9e92ebbc31a50c99c3a04719284.json new file mode 100644 index 0000000000..13f1319f04 --- /dev/null +++ b/backend/.sqlx/query-a69ba7471d1a5faa145bebbd17d43e6dbe02e9e92ebbc31a50c99c3a04719284.json @@ -0,0 +1,87 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH uuid_table as (\n select gen_random_uuid() as uuid from generate_series(1, $11)\n )\n INSERT INTO queue \n (id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout, raw_code, raw_lock, raw_flow, flow_status)\n (SELECT uuid, $1, $2, $3, $4, ('{ \"uuid\": \"' || uuid || '\" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14, $15, $16, $17, $18 FROM uuid_table) \n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Int8", + "Varchar", + { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow" + ] + } + } + }, + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible" + ] + } + } + }, + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Varchar", + "Int4", + "Int4", + "Int4", + "Int4", + "Text", + "Text", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [ + false + ] + }, + "hash": "a69ba7471d1a5faa145bebbd17d43e6dbe02e9e92ebbc31a50c99c3a04719284" +} diff --git a/backend/.sqlx/query-a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d.json b/backend/.sqlx/query-a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d.json new file mode 100644 index 0000000000..53ef15ffa5 --- /dev/null +++ b/backend/.sqlx/query-a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE alerts SET acknowledged = true WHERE acknowledged = false", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d" +} diff --git a/backend/.sqlx/query-ae543dfa106fa6ad4e9bf45cda1110d4702a80f455c63af6fcbd7cf45bbc4f7a.json b/backend/.sqlx/query-ae543dfa106fa6ad4e9bf45cda1110d4702a80f455c63af6fcbd7cf45bbc4f7a.json new file mode 100644 index 0000000000..c97780ba06 --- /dev/null +++ b/backend/.sqlx/query-ae543dfa106fa6ad4e9bf45cda1110d4702a80f455c63af6fcbd7cf45bbc4f7a.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT flow_version.value AS \"value: sqlx::types::Json>\" FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 AND flow.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value: sqlx::types::Json>", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "ae543dfa106fa6ad4e9bf45cda1110d4702a80f455c63af6fcbd7cf45bbc4f7a" +} diff --git a/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json b/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json new file mode 100644 index 0000000000..ba9f3814e8 --- /dev/null +++ b/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n hostname,\n mode::text,\n worker_group,\n log_ts,\n file_path,\n ok_lines,\n err_lines,\n json_fmt\n FROM log_file\n WHERE log_ts > $1\n ORDER BY log_ts ASC LIMIT $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hostname", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "worker_group", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "log_ts", + "type_info": "Timestamp" + }, + { + "ordinal": 4, + "name": "file_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "ok_lines", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "err_lines", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "json_fmt", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Timestamp", + "Int8" + ] + }, + "nullable": [ + false, + null, + true, + false, + false, + true, + true, + true + ] + }, + "hash": "b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a" +} diff --git a/backend/.sqlx/query-be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580.json b/backend/.sqlx/query-be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580.json new file mode 100644 index 0000000000..e726f11fe2 --- /dev/null +++ b/backend/.sqlx/query-be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE alerts SET acknowledged = true WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [] + }, + "hash": "be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580" +} diff --git a/backend/.sqlx/query-02f1a6eeb27067dc438459238e7b016f5ccf9e3fe0ffbe88471f15aad8f74441.json b/backend/.sqlx/query-c9930fcfe79541af570eace58ba7e15a0816a6b4fd036cf7b991a210654b2633.json similarity index 83% rename from backend/.sqlx/query-02f1a6eeb27067dc438459238e7b016f5ccf9e3fe0ffbe88471f15aad8f74441.json rename to backend/.sqlx/query-c9930fcfe79541af570eace58ba7e15a0816a6b4fd036cf7b991a210654b2633.json index a27bc6365a..da7b44d1b9 100644 --- a/backend/.sqlx/query-02f1a6eeb27067dc438459238e7b016f5ccf9e3fe0ffbe88471f15aad8f74441.json +++ b/backend/.sqlx/query-c9930fcfe79541af570eace58ba7e15a0816a6b4fd036cf7b991a210654b2633.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\" FROM http_trigger WHERE workspace_id = $1", + "query": "SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as \"http_method: _\", static_asset_config as \"static_asset_config: _\" FROM http_trigger WHERE workspace_id = $1", "describe": { "columns": [ { @@ -65,6 +65,11 @@ } } } + }, + { + "ordinal": 10, + "name": "static_asset_config: _", + "type_info": "Jsonb" } ], "parameters": { @@ -82,8 +87,9 @@ false, false, false, - false + false, + true ] }, - "hash": "02f1a6eeb27067dc438459238e7b016f5ccf9e3fe0ffbe88471f15aad8f74441" + "hash": "c9930fcfe79541af570eace58ba7e15a0816a6b4fd036cf7b991a210654b2633" } diff --git a/backend/.sqlx/query-cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b.json b/backend/.sqlx/query-cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b.json new file mode 100644 index 0000000000..18bc159e6c --- /dev/null +++ b/backend/.sqlx/query-cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n ORDER BY created_at DESC \n LIMIT $1 OFFSET $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "alert_type", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "message", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "acknowledged", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + true + ] + }, + "hash": "cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b" +} diff --git a/backend/.sqlx/query-cd33a9d63f4706a7e3b1e23cd0a4b2e3ecb30aae6510d1fcd08493b07c8b0952.json b/backend/.sqlx/query-cd33a9d63f4706a7e3b1e23cd0a4b2e3ecb30aae6510d1fcd08493b07c8b0952.json new file mode 100644 index 0000000000..36b1323980 --- /dev/null +++ b/backend/.sqlx/query-cd33a9d63f4706a7e3b1e23cd0a4b2e3ecb30aae6510d1fcd08493b07c8b0952.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET enabled = FALSE, error = $1, server_id = NULL, last_server_ping = NULL WHERE workspace_id = $2 AND path = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "cd33a9d63f4706a7e3b1e23cd0a4b2e3ecb30aae6510d1fcd08493b07c8b0952" +} diff --git a/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json b/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json new file mode 100644 index 0000000000..b64db838d3 --- /dev/null +++ b/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n hostname,\n mode::text,\n worker_group,\n log_ts,\n file_path,\n ok_lines,\n err_lines,\n json_fmt\n FROM log_file\n ORDER BY log_ts ASC LIMIT $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hostname", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "worker_group", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "log_ts", + "type_info": "Timestamp" + }, + { + "ordinal": 4, + "name": "file_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "ok_lines", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "err_lines", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "json_fmt", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + null, + true, + false, + false, + true, + true, + true + ] + }, + "hash": "dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6" +} diff --git a/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json b/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json deleted file mode 100644 index 2e75b4e74b..0000000000 --- a/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT *\n FROM websocket_trigger\n WHERE workspace_id = $1 AND path = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "url", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "script_path", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "is_flow", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "edited_by", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "edited_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "server_id", - "type_info": "Varchar" - }, - { - "ordinal": 10, - "name": "last_server_ping", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "error", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "enabled", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "filters", - "type_info": "JsonbArray" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - true, - true, - false, - false - ] - }, - "hash": "f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021" -} diff --git a/backend/.sqlx/query-4fb95eae1c871241efe2ef79615ce03cba0e4a12aad3274e4829d98e38ca1491.json b/backend/.sqlx/query-f904702536c106b0e5da8facae119c6af887c49a29ae44b3a95350ff27fb1ccf.json similarity index 83% rename from backend/.sqlx/query-4fb95eae1c871241efe2ef79615ce03cba0e4a12aad3274e4829d98e38ca1491.json rename to backend/.sqlx/query-f904702536c106b0e5da8facae119c6af887c49a29ae44b3a95350ff27fb1ccf.json index d954f2c26c..08592a3f04 100644 --- a/backend/.sqlx/query-4fb95eae1c871241efe2ef79615ce03cba0e4a12aad3274e4829d98e38ca1491.json +++ b/backend/.sqlx/query-f904702536c106b0e5da8facae119c6af887c49a29ae44b3a95350ff27fb1ccf.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as \"http_method: _\", edited_by, email, edited_at, extra_perms, is_async, requires_auth\n FROM http_trigger\n WHERE workspace_id = $1 AND path = $2", + "query": "SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as \"http_method: _\", edited_by, email, edited_at, extra_perms, is_async, requires_auth, static_asset_config as \"static_asset_config: _\"\n FROM http_trigger\n WHERE workspace_id = $1 AND path = $2", "describe": { "columns": [ { @@ -80,6 +80,11 @@ "ordinal": 12, "name": "requires_auth", "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "static_asset_config: _", + "type_info": "Jsonb" } ], "parameters": { @@ -101,8 +106,9 @@ false, false, false, - false + false, + true ] }, - "hash": "4fb95eae1c871241efe2ef79615ce03cba0e4a12aad3274e4829d98e38ca1491" + "hash": "f904702536c106b0e5da8facae119c6af887c49a29ae44b3a95350ff27fb1ccf" } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d12b1b2698..56bca6cb08 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arc-swap" @@ -441,7 +441,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -511,8 +511,8 @@ checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.4.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -527,21 +527,21 @@ dependencies = [ "async-io", "async-lock", "blocking", - "futures-lite 2.4.0", + "futures-lite 2.5.0", "once_cell", ] [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.4.0", + "futures-lite 2.5.0", "parking", "polling", "rustix", @@ -588,7 +588,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -605,7 +605,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "futures-lite 2.4.0", + "futures-lite 2.5.0", "gloo-timers", "kv-log-macro", "log", @@ -636,7 +636,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -678,7 +678,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -732,9 +732,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.9" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6448cfb224dd6a9b9ac734f58622dd0d4751f3589f3b777345745f46b2eb14" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" dependencies = [ "aws-credential-types", "aws-runtime", @@ -749,7 +749,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "hex", "http 0.2.12", "ring 0.17.8", @@ -787,7 +787,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -799,9 +799,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded855583fa1d22e88fe39fd6062b062376e50a8211989e07cf5e38d52eb3453" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -821,9 +821,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9177ea1192e6601ae16c7273385690d88a7ed386a00b74a6bc894d12103cd933" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" dependencies = [ "aws-credential-types", "aws-runtime", @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823ef553cf36713c97453e2ddff1eb8f62be7f4523544e2a5db64caf80100f0a" +checksum = "53dcf5e7d9bd1517b8b998e170e650047cea8a2b85fe1835abe3210713e541b7" dependencies = [ "aws-credential-types", "aws-runtime", @@ -948,7 +948,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -966,9 +966,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.8" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c9cdc179e6afbf5d391ab08c85eac817b51c87e1892a5edb5f7bbdc64314b4" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd 0.8.0", "bytes", @@ -1218,7 +1218,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.86", + "syn 2.0.87", "which 4.4.2", ] @@ -1237,7 +1237,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1357,15 +1357,15 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.4.0", + "futures-lite 2.5.0", "piper", ] [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "f5327f6c99920069d1fe374aa743be1af0031dea9f250852cdf1ae6a0861ee24" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1373,16 +1373,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "10aedd8f1a81a8aafbfde924b0e3061cd6fedd6f6bbcfc6a76e6fd426d7bfe26" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.86", - "syn_derive", + "syn 2.0.87", ] [[package]] @@ -1484,7 +1483,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1599,9 +1598,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.33" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3788d6ac30243803df38a3e9991cf37e41210232916d41a8222ae378f912624" +checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" dependencies = [ "jobserver", "libc", @@ -1744,7 +1743,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1790,7 +1789,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1817,7 +1816,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] @@ -1944,9 +1943,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1986,9 +1985,9 @@ dependencies = [ [[package]] name = "cron" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" +checksum = "eee8b2b4516038bc0f1d3c9934bcb4a13dd316e04abbc63c96757a6d75978532" dependencies = [ "chrono", "nom", @@ -2081,9 +2080,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -2124,7 +2123,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2172,7 +2171,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2194,7 +2193,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2554,7 +2553,7 @@ dependencies = [ "once_cell", "percent-encoding", "serde", - "sourcemap 9.0.0", + "sourcemap 9.0.1", "swc_atoms", "swc_common", "swc_config", @@ -2578,7 +2577,7 @@ dependencies = [ "swc_visit_macros", "text_lines", "thiserror", - "unicode-width", + "unicode-width 0.1.14", "url", ] @@ -2715,7 +2714,7 @@ dependencies = [ "quote", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.86", + "syn 2.0.87", "thiserror", ] @@ -2907,7 +2906,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2942,13 +2941,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", ] [[package]] @@ -2961,6 +2969,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -2984,6 +3003,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlopen2" version = "0.7.0" @@ -3004,7 +3034,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3160,7 +3190,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3180,7 +3210,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3275,9 +3305,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "ff" @@ -3395,7 +3425,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3427,7 +3457,7 @@ checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3439,7 +3469,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3451,7 +3481,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3556,11 +3586,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -3575,7 +3605,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3778,7 +3808,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -3828,7 +3858,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3976,9 +4006,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ "allocator-api2", "equivalent", @@ -4030,7 +4060,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b780635574b3d92f036890d8373433d6f9fc7abb320ee42a5c25897fc8ed732" dependencies = [ - "dirs", + "dirs 5.0.1", "indicatif", "log", "native-tls", @@ -4345,6 +4375,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -4363,12 +4511,23 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -4395,21 +4554,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", "serde", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -4456,14 +4615,14 @@ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-macro" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2069faacbe981460232f880d26bf3c7634e322d49053aa48c27e3ae642f728f1" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" dependencies = [ - "Inflector", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -4672,9 +4831,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.161" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libgit2-sys" @@ -4761,6 +4920,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -4796,7 +4961,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.1", ] [[package]] @@ -5113,7 +5278,7 @@ checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -5141,18 +5306,18 @@ checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] name = "mysql-common-derive" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe0450cc9344afff34915f8328600ab5ae19260802a334d0f72d2d5bdda3bfe" +checksum = "63c3512cf11487168e0e9db7157801bf5273be13055a9cc95356dc9e0035e49c" dependencies = [ "darling 0.20.10", - "heck 0.4.1", + "heck 0.5.0", "num-bigint", "proc-macro-crate", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "termcolor", "thiserror", ] @@ -5562,7 +5727,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -5839,7 +6004,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -5880,7 +6045,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -5902,7 +6067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-io", ] @@ -5957,9 +6122,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", @@ -6062,7 +6227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -6084,27 +6249,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.87", ] [[package]] @@ -6115,7 +6278,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -6127,7 +6290,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -6188,7 +6351,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "syn 2.0.86", + "syn 2.0.87", "thiserror", "typify", "unicode-ident", @@ -6208,7 +6371,7 @@ dependencies = [ "serde_json", "serde_tokenstream", "serde_yaml", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -6251,7 +6414,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -6391,9 +6554,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e346e016eacfff12233c243718197ca12f148c84e1e84268a896699b41c71780" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ "cfg_aliases", "libc", @@ -6610,7 +6773,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -6625,9 +6788,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -6935,7 +7098,8 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.86", + "shellexpand", + "syn 2.0.87", "walkdir", ] @@ -7027,9 +7191,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -7315,7 +7479,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -7375,9 +7539,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -7415,9 +7579,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -7456,13 +7620,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -7473,7 +7637,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -7548,7 +7712,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -7603,7 +7767,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -7669,6 +7833,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs 4.0.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -7870,9 +8043,9 @@ dependencies = [ [[package]] name = "sourcemap" -version = "9.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab08a862c70980b8e23698b507e272317ae52a608a164a844111f5372374f1f" +checksum = "86721155737d89818020dc35713595c6e7b21016073c101e6590c47ca58c2d16" dependencies = [ "base64-simd 0.7.0", "bitvec", @@ -7972,7 +8145,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8043,7 +8216,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8066,7 +8239,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.86", + "syn 2.0.87", "tempfile", "tokio", "url", @@ -8218,7 +8391,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8272,7 +8445,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8285,7 +8458,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8360,13 +8533,13 @@ dependencies = [ "rustc-hash 1.1.0", "serde", "siphasher", - "sourcemap 9.0.0", + "sourcemap 9.0.1", "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", "tracing", - "unicode-width", + "unicode-width 0.1.14", "url", ] @@ -8393,7 +8566,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8424,7 +8597,7 @@ dependencies = [ "num-bigint", "once_cell", "serde", - "sourcemap 9.0.0", + "sourcemap 9.0.1", "swc_allocator", "swc_atoms", "swc_common", @@ -8442,7 +8615,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8527,7 +8700,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8634,7 +8807,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8645,7 +8818,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8668,7 +8841,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8684,27 +8857,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.86" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.86", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -8728,7 +8889,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -8947,12 +9108,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand 2.2.0", "once_cell", "rustix", "windows-sys 0.59.0", @@ -8978,22 +9139,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -9127,6 +9288,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -9218,7 +9389,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -9545,7 +9716,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -9782,7 +9953,7 @@ dependencies = [ "regress", "schemars", "serde_json", - "syn 2.0.86", + "syn 2.0.87", "thiserror", "unicode-ident", ] @@ -9799,7 +9970,7 @@ dependencies = [ "serde", "serde_json", "serde_tokenstream", - "syn 2.0.86", + "syn 2.0.87", "typify-impl", ] @@ -9945,6 +10116,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -10018,12 +10195,12 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", "serde", ] @@ -10052,12 +10229,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-ranges" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -10194,7 +10383,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -10228,7 +10417,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10261,7 +10450,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -10395,11 +10584,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "axum", - "base64 0.21.7", + "base64 0.22.1", "chrono", "deno_core", "dotenv", @@ -10437,7 +10626,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "argon2", @@ -10448,7 +10637,7 @@ dependencies = [ "async_zip", "axum", "base32", - "base64 0.21.7", + "base64 0.22.1", "bytes", "candle-core", "candle-nn", @@ -10522,9 +10711,9 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.416.2" +version = "1.423.1" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "openapiv3", "prettyplease 0.1.25", @@ -10540,7 +10729,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.416.2" +version = "1.423.1" dependencies = [ "chrono", "serde", @@ -10553,7 +10742,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "rsmq_async", @@ -10568,7 +10757,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "async-stream", @@ -10614,7 +10803,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.416.2" +version = "1.423.1" dependencies = [ "regex", "rsmq_async", @@ -10629,7 +10818,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "bytes", @@ -10651,19 +10840,19 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.416.2" +version = "1.423.1" dependencies = [ "itertools 0.13.0", "lazy_static", "proc-macro2", "quote", "regex", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] name = "windmill-parser" -version = "1.416.2" +version = "1.423.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -10672,7 +10861,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "lazy_static", @@ -10684,7 +10873,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "gosyn", @@ -10696,7 +10885,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "lazy_static", @@ -10708,7 +10897,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10719,7 +10908,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10730,7 +10919,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "async-recursion", @@ -10748,7 +10937,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10758,14 +10947,14 @@ dependencies = [ "quote", "regex", "serde_json", - "syn 2.0.86", + "syn 2.0.87", "toml 0.7.8", "windmill-parser", ] [[package]] name = "windmill-parser-sql" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "lazy_static", @@ -10777,7 +10966,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "lazy_static", @@ -10795,7 +10984,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10816,7 +11005,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "serde_json", @@ -10826,7 +11015,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "async-recursion", @@ -10859,7 +11048,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.416.2" +version = "1.423.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -10869,11 +11058,11 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.416.2" +version = "1.423.1" dependencies = [ "anyhow", "async-recursion", - "base64 0.21.7", + "base64 0.22.1", "bit-vec", "bytes", "chrono", @@ -11165,6 +11354,18 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -11235,7 +11436,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "synstructure", ] @@ -11257,7 +11458,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -11277,7 +11478,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "synstructure", ] @@ -11287,6 +11488,28 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 1c0abc299c..3196a3ce84 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.416.2" +version = "1.423.1" authors.workspace = true edition.workspace = true @@ -29,7 +29,7 @@ members = [ ] [workspace.package] -version = "1.416.2" +version = "1.423.1" authors = ["Ruben Fiszel "] edition = "2021" @@ -154,7 +154,7 @@ tracing-appender = "^0" prometheus = { version = "^0", default-features = false } cookie = { version = "0.17.0" } phf = { version = "0.11", features = ["macros"] } -rust-embed = "^6" +rust-embed = { version = "^6", features = ["interpolate-folder-path"] } mime_guess = "^2" hex = "^0" sql-builder = "^3" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index e7dd9365ec..32671f9c64 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -f136a2f499e0fe7c10c54c79488851980d796eb2 \ No newline at end of file +053cf0a539c64efbe8eee0e2a330b72114a43193 \ No newline at end of file diff --git a/backend/migrations/20241030150214_ws_auth.down.sql b/backend/migrations/20241030150214_ws_auth.down.sql new file mode 100644 index 0000000000..6fed6a173b --- /dev/null +++ b/backend/migrations/20241030150214_ws_auth.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER TABLE websocket_trigger DROP COLUMN initial_messages, DROP COLUMN url_runnable_args; \ No newline at end of file diff --git a/backend/migrations/20241030150214_ws_auth.up.sql b/backend/migrations/20241030150214_ws_auth.up.sql new file mode 100644 index 0000000000..ddefe851e5 --- /dev/null +++ b/backend/migrations/20241030150214_ws_auth.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE websocket_trigger ADD COLUMN initial_messages JSONB[] DEFAULT '{}', ADD COLUMN url_runnable_args JSONB DEFAULT '{}'; \ No newline at end of file diff --git a/backend/migrations/20241105225151_alert_add_acknowledged.down.sql b/backend/migrations/20241105225151_alert_add_acknowledged.down.sql new file mode 100644 index 0000000000..ed9a1323d6 --- /dev/null +++ b/backend/migrations/20241105225151_alert_add_acknowledged.down.sql @@ -0,0 +1 @@ +ALTER TABLE alerts DROP COLUMN acknowledged; \ No newline at end of file diff --git a/backend/migrations/20241105225151_alert_add_acknowledged.up.sql b/backend/migrations/20241105225151_alert_add_acknowledged.up.sql new file mode 100644 index 0000000000..5174aec6e0 --- /dev/null +++ b/backend/migrations/20241105225151_alert_add_acknowledged.up.sql @@ -0,0 +1,9 @@ +-- Step 1: Add the new column 'acknowledged' to the 'alerts' table +ALTER TABLE alerts +ADD COLUMN acknowledged BOOLEAN DEFAULT NULL; + +-- Step 2: Update all existing rows to set 'acknowledged' to true +-- we don't want to pop up notifications to all users after migrations +-- but only show new alerts from the point of the upgrade +UPDATE alerts +SET acknowledged = TRUE; \ No newline at end of file diff --git a/backend/migrations/20241107163553_http_route_static_asset.down.sql b/backend/migrations/20241107163553_http_route_static_asset.down.sql new file mode 100644 index 0000000000..b16c347028 --- /dev/null +++ b/backend/migrations/20241107163553_http_route_static_asset.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER TABLE http_trigger DROP COLUMN static_asset_config; \ No newline at end of file diff --git a/backend/migrations/20241107163553_http_route_static_asset.up.sql b/backend/migrations/20241107163553_http_route_static_asset.up.sql new file mode 100644 index 0000000000..fdd1a25b71 --- /dev/null +++ b/backend/migrations/20241107163553_http_route_static_asset.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE http_trigger ADD COLUMN static_asset_config JSONB; \ No newline at end of file diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json index 994d9999fd..0608f0d3c4 100644 --- a/backend/oauth_connect.json +++ b/backend/oauth_connect.json @@ -171,5 +171,6 @@ "user-library-modify", "user-library-read" ] - } -} \ No newline at end of file + }, + "snowflake_oauth": {} +} diff --git a/backend/parsers/windmill-parser-py-imports/src/lib.rs b/backend/parsers/windmill-parser-py-imports/src/lib.rs index 841a763ec2..38869fcd39 100644 --- a/backend/parsers/windmill-parser-py-imports/src/lib.rs +++ b/backend/parsers/windmill-parser-py-imports/src/lib.rs @@ -62,6 +62,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma "lokalise" => "python-lokalise-api", "msgraph" => "msgraph-sdk", "pythonjsonlogger" => "python-json-logger", + "socks" => "PySocks", }; fn replace_import(x: String) -> String { diff --git a/backend/src/main.rs b/backend/src/main.rs index cc24051a7f..7db76f49b1 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -36,7 +36,7 @@ use windmill_common::{ ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, - OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING, }, @@ -78,7 +78,7 @@ use crate::monitor::{ reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config, - reload_worker_config, + reload_worker_config, reload_critical_alert_mute_ui_setting, }; #[cfg(feature = "parquet")] @@ -531,7 +531,7 @@ Windmill Community Edition {GIT_VERSION} #[cfg(feature = "tantivy")] let (index_reader, index_writer) = if should_index_jobs { - let (r, w) = windmill_indexer::indexer_ee::init_index(&db).await?; + let (r, w) = windmill_indexer::completed_runs_ee::init_index(&db).await?; (Some(r), Some(w)) } else { (None, None) @@ -543,26 +543,61 @@ Windmill Community Edition {GIT_VERSION} let index_writer2 = index_writer.clone(); async { if let Some(index_writer) = index_writer2 { - windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx) - .await; + windmill_indexer::completed_runs_ee::run_indexer( + db.clone(), + index_writer, + indexer_rx, + ) + .await; + } + Ok(()) + } + }; + + #[cfg(all(feature = "tantivy", feature = "parquet"))] + let (log_index_reader, log_index_writer) = if should_index_jobs { + let (r, w) = windmill_indexer::service_logs_ee::init_index(&db).await?; + (Some(r), Some(w)) + } else { + (None, None) + }; + + #[cfg(all(feature = "tantivy", feature = "parquet"))] + let log_indexer_f = { + let log_indexer_rx = killpill_rx.resubscribe(); + let log_index_writer2 = log_index_writer.clone(); + async { + if let Some(log_index_writer) = log_index_writer2 { + windmill_indexer::service_logs_ee::run_indexer( + db.clone(), + log_index_writer, + log_indexer_rx, + ) + .await; } Ok(()) } }; #[cfg(not(feature = "tantivy"))] - let (index_reader, index_writer) = (None, None); + let index_reader = None; #[cfg(not(feature = "tantivy"))] let indexer_f = async { Ok(()) as anyhow::Result<()> }; + #[cfg(not(all(feature = "tantivy", feature = "parquet")))] + let log_index_reader = None; + + #[cfg(not(all(feature = "tantivy", feature = "parquet")))] + let log_indexer_f = async { Ok(()) as anyhow::Result<()> }; + let server_f = async { if !is_agent { windmill_api::run_server( db.clone(), rsmq2, index_reader, - index_writer, + log_index_reader, addr, server_killpill_rx, base_internal_tx, @@ -782,6 +817,12 @@ Windmill Community Edition {GIT_VERSION} tracing::error!(error = %e, "Could not reload jwt secret setting"); } }, + CRITICAL_ALERT_MUTE_UI_SETTING => { + tracing::info!("Critical alert UI setting changed"); + if let Err(e) = reload_critical_alert_mute_ui_setting(&db).await { + tracing::error!(error = %e, "Could not reload critical alert UI setting"); + } + }, a @_ => { tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a); } @@ -842,7 +883,8 @@ Windmill Community Edition {GIT_VERSION} monitor_f, server_f, metrics_f, - indexer_f + indexer_f, + log_indexer_f )?; } else { tracing::info!("Nothing to do, exiting."); diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 0618cfd8d8..da811bb6ff 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -39,7 +39,7 @@ use windmill_common::{ EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING, - PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING, }, @@ -55,7 +55,7 @@ use windmill_common::{ WORKER_GROUP, }, BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, - METRICS_DEBUG_ENABLED, METRICS_ENABLED, + METRICS_DEBUG_ENABLED, METRICS_ENABLED, CRITICAL_ALERT_MUTE_UI_ENABLED }; use windmill_queue::cancel_job; use windmill_worker::{ @@ -111,6 +111,9 @@ lazy_static::lazy_static! { "Number of jobs in the queue", &["tag"] ).unwrap(); + + static ref QUEUE_COUNT_TAGS: Arc>> = Arc::new(RwLock::new(Vec::new())); + } pub async fn initial_load( @@ -128,6 +131,10 @@ pub async fn initial_load( tracing::error!("Error loading expose debug metrics: {e:#}"); } + if let Err(e) = reload_critical_alert_mute_ui_setting(db).await { + tracing::error!("Error loading critical alert mute ui setting: {e:#}"); + } + if let Err(e) = load_tag_per_workspace_enabled(db).await { tracing::error!("Error loading default tag per workpsace: {e:#}"); } @@ -223,6 +230,17 @@ pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> { Ok(()) } +pub async fn reload_critical_alert_mute_ui_setting(db: &DB) -> error::Result<()> { + let mute = load_value_from_global_settings(db, CRITICAL_ALERT_MUTE_UI_SETTING).await; + match mute { + Ok(Some(serde_json::Value::Bool(t))) => { + CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed); + } + _ => (), + }; + Ok(()) +} + pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> { let metrics_enabled = load_value_from_global_settings(db, EXPOSE_DEBUG_METRICS_SETTING).await; match metrics_enabled { @@ -1089,12 +1107,23 @@ pub async fn expose_queue_metrics(db: &Pool) { if metrics_enabled || save_metrics { let queue_counts = windmill_common::queue::get_queue_counts(db).await; + if metrics_enabled { + for q in QUEUE_COUNT_TAGS.read().await.iter() { + if queue_counts.get(q).is_none() { + (*QUEUE_COUNT).with_label_values(&[q]).set(0); + } + } + } + + let mut tags_to_watch = vec![]; for q in queue_counts { let count = q.1; let tag = q.0; + if metrics_enabled { let metric = (*QUEUE_COUNT).with_label_values(&[&tag]); metric.set(count as i64); + tags_to_watch.push(tag.to_string()); } // save queue_count and delay metrics per tag @@ -1119,6 +1148,10 @@ pub async fn expose_queue_metrics(db: &Pool) { } } } + if metrics_enabled { + let mut w = QUEUE_COUNT_TAGS.write().await; + *w = tags_to_watch; + } } // clean queue metrics older than 14 days diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index a93c6eef1d..69482f3922 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -1112,6 +1112,7 @@ async fn test_deno_flow(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, } .into(), stop_after_if: Default::default(), @@ -1153,6 +1154,7 @@ async fn test_deno_flow(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, } .into(), stop_after_if: Default::default(), @@ -1276,6 +1278,8 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, + }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1327,6 +1331,7 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1364,6 +1369,8 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, + }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1424,6 +1431,7 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), diff --git a/backend/windmill-api/openapi-deref.json b/backend/windmill-api/openapi-deref.json index db789c2163..a6e225259a 100644 --- a/backend/windmill-api/openapi-deref.json +++ b/backend/windmill-api/openapi-deref.json @@ -1,7 +1,7 @@ { "openapi": "3.0.3", "info": { - "version": "1.409.0", + "version": "1.418.0", "title": "Windmill API", "contact": { "name": "Windmill Team", @@ -1182,6 +1182,115 @@ } } }, + "/settings/critical_alerts": { + "get": { + "summary": "Get all critical alerts", + "operationId": "getCriticalAlerts", + "tags": [ + "setting" + ], + "parameters": [ + { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1, + "description": "The page number to retrieve (minimum value is 1)" + } + }, + { + "in": "query", + "name": "page_size", + "schema": { + "type": "integer", + "default": 10, + "maximum": 100, + "description": "Number of alerts per page (maximum is 100)" + } + }, + { + "in": "query", + "name": "acknowledged", + "schema": { + "type": "boolean", + "nullable": true, + "description": "Filter by acknowledgment status; true for acknowledged, false for unacknowledged, and omit for all alerts" + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved all critical alerts", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CriticalAlert" + } + } + } + } + } + } + } + }, + "/settings/critical_alerts/{id}/acknowledge": { + "post": { + "summary": "Acknowledge a critical alert", + "operationId": "acknowledgeCriticalAlert", + "tags": [ + "setting" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + }, + "description": "The ID of the critical alert to acknowledge" + } + ], + "responses": { + "200": { + "description": "Successfully acknowledged the critical alert", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "Critical alert acknowledged" + } + } + } + } + } + } + }, + "/settings/critical_alerts/acknowledge_all": { + "post": { + "summary": "Acknowledge all unacknowledged critical alerts", + "operationId": "acknowledgeAllCriticalAlerts", + "tags": [ + "setting" + ], + "responses": { + "200": { + "description": "Successfully acknowledged all unacknowledged critical alerts.", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "All unacknowledged critical alerts acknowledged" + } + } + } + } + } + } + }, "/settings/test_license_key": { "post": { "summary": "test license key", @@ -2239,6 +2348,14 @@ }, { "$ref": "#/components/parameters/PerPage" + }, + { + "name": "active_only", + "in": "query", + "description": "filter only active users", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -3328,6 +3445,44 @@ } } }, + "/w/{workspace}/workspaces/used_triggers": { + "get": { + "summary": "get used triggers", + "operationId": "getUsedTriggers", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "http_routes_used": { + "type": "boolean" + }, + "websocket_used": { + "type": "boolean" + } + }, + "required": [ + "http_routes_used", + "websocket_used" + ] + } + } + } + } + } + } + }, "/w/{workspace}/users/list": { "get": { "summary": "list users", @@ -6362,6 +6517,36 @@ } } }, + "/w/{workspace}/scripts/get_latest_version/{path}": { + "get": { + "summary": "get scripts's latest version (hash)", + "operationId": "getScriptLatestVersion", + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "tags": [ + "script" + ], + "responses": { + "200": { + "description": "Script version/hash", + "content": { + "application/json": { + "required": false, + "schema": { + "$ref": "#/components/schemas/ScriptHistory" + } + } + } + } + } + } + }, "/w/{workspace}/scripts/history_update/h/{hash}/p/{path}": { "post": { "summary": "update history of a script", @@ -7178,6 +7363,36 @@ } } }, + "/w/{workspace}/flows/get_latest_version/{path}": { + "get": { + "summary": "get flow's latest version", + "operationId": "getFlowLatestVersion", + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "tags": [ + "flow" + ], + "responses": { + "200": { + "description": "Flow version", + "content": { + "application/json": { + "required": false, + "schema": { + "$ref": "#/components/schemas/FlowVersion" + } + } + } + } + } + } + }, "/w/{workspace}/flows/get/v/{version}/p/{path}": { "get": { "summary": "get flow version", @@ -8096,6 +8311,36 @@ } } }, + "/w/{workspace}/apps/get_latest_version/{path}": { + "get": { + "summary": "get apps's latest version", + "operationId": "getAppLatestVersion", + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/ScriptPath" + } + ], + "tags": [ + "app" + ], + "responses": { + "200": { + "description": "App version", + "content": { + "application/json": { + "required": false, + "schema": { + "$ref": "#/components/schemas/AppHistory" + } + } + } + } + } + } + }, "/w/{workspace}/apps/history_update/a/{id}/v/{version}": { "post": { "summary": "update app history", @@ -11495,21 +11740,217 @@ } } }, - "/w/{workspace}/http_triggers/used": { - "get": { - "summary": "whether http triggers are used", - "operationId": "used", + "/w/{workspace}/websocket_triggers/create": { + "post": { + "summary": "create websocket trigger", + "operationId": "createWebsocketTrigger", "tags": [ - "http_trigger" + "websocket_trigger" ], "parameters": [ { "$ref": "#/components/parameters/WorkspaceId" } ], + "requestBody": { + "description": "new websocket trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewWebsocketTrigger" + } + } + } + }, + "responses": { + "201": { + "description": "websocket trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/websocket_triggers/update/{path}": { + "post": { + "summary": "update websocket trigger", + "operationId": "updateWebsocketTrigger", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditWebsocketTrigger" + } + } + } + }, "responses": { "200": { - "description": "whether http triggers are used", + "description": "websocket trigger updated", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/websocket_triggers/delete/{path}": { + "delete": { + "summary": "delete websocket trigger", + "operationId": "deleteWebsocketTrigger", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "websocket trigger deleted", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/websocket_triggers/get/{path}": { + "get": { + "summary": "get websocket trigger", + "operationId": "getWebsocketTrigger", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "websocket trigger deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebsocketTrigger" + } + } + } + } + } + } + }, + "/w/{workspace}/websocket_triggers/list": { + "get": { + "summary": "list websocket triggers", + "operationId": "listWebsocketTriggers", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId", + "required": true + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PerPage" + }, + { + "name": "path", + "description": "filter by path", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "is_flow", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "path_start", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "websocket trigger list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebsocketTrigger" + } + } + } + } + } + } + } + }, + "/w/{workspace}/websocket_triggers/exists/{path}": { + "get": { + "summary": "does websocket trigger exists", + "operationId": "existsWebsocketTrigger", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "websocket trigger exists", "content": { "application/json": { "schema": { @@ -11521,6 +11962,54 @@ } } }, + "/w/{workspace}/websocket_triggers/setenabled/{path}": { + "post": { + "summary": "set enabled websocket trigger", + "operationId": "setWebsocketTriggerEnabled", + "tags": [ + "websocket_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "requestBody": { + "description": "updated websocket trigger enable", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + } + } + } + }, + "responses": { + "200": { + "description": "websocket trigger enabled set", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/groups/list": { "get": { "summary": "list instance groups", @@ -12812,6 +13301,40 @@ } } }, + "/configs/list_autoscaling_events/{worker_group}": { + "get": { + "summary": "List autoscaling events", + "operationId": "listAutoscalingEvents", + "tags": [ + "config" + ], + "parameters": [ + { + "name": "worker_group", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of autoscaling events", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutoscalingEvent" + } + } + } + } + } + } + } + }, "/w/{workspace}/acls/get/{kind}/{path}": { "get": { "summary": "get granular acls", @@ -12842,7 +13365,8 @@ "folder", "app", "raw_app", - "http_trigger" + "http_trigger", + "websocket_trigger" ] } } @@ -12894,7 +13418,8 @@ "folder", "app", "raw_app", - "http_trigger" + "http_trigger", + "websocket_trigger" ] } } @@ -12965,7 +13490,8 @@ "folder", "app", "raw_app", - "http_trigger" + "http_trigger", + "websocket_trigger" ] } } @@ -14959,6 +15485,165 @@ } } } + }, + "/srch/index/search/service_logs": { + "get": { + "summary": "Search through service logs with a string query", + "operationId": "searchLogsIndex", + "tags": [ + "indexSearch" + ], + "parameters": [ + { + "name": "search_query", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "mode", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "worker_group", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "hostname", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "min_ts", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "max_ts", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "search results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "query_parse_errors": { + "description": "a list of the terms that couldn't be parsed (and thus ignored)", + "type": "array", + "items": { + "type": "string" + } + }, + "hits": { + "description": "log files that matched the query", + "type": "array", + "items": { + "$ref": "#/components/schemas/LogSearchHit" + } + } + } + } + } + } + } + } + } + }, + "/srch/index/search/count_service_logs": { + "get": { + "summary": "Search and count the log line hits on every provided host", + "operationId": "countSearchLogsIndex", + "tags": [ + "indexSearch" + ], + "parameters": [ + { + "name": "search_query", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "hosts", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "min_ts", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "max_ts", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "search results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "query_parse_errors": { + "description": "a list of the terms that couldn't be parsed (and thus ignored)", + "type": "array", + "items": { + "type": "string" + } + }, + "count_per_host": { + "description": "count of log lines that matched the query per hostname", + "type": "object" + } + } + } + } + } + } + } + } } }, "components": { @@ -16560,6 +17245,7 @@ "users.delete", "users.update", "users.login", + "users.login_failure", "users.logout", "users.accept_invite", "users.decline_invite", @@ -16571,6 +17257,7 @@ "users.impersonate", "users.leave_workspace", "oauth.login", + "oauth.login_failure", "oauth.signup", "variables.create", "variables.delete", @@ -17533,9 +18220,248 @@ }, "email_count": { "type": "number" + }, + "websocket_count": { + "type": "number" } } }, + "WebsocketTrigger": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "edited_by": { + "type": "string" + }, + "edited_at": { + "type": "string", + "format": "date-time" + }, + "script_path": { + "type": "string" + }, + "url": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "extra_perms": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "email": { + "type": "string" + }, + "workspace_id": { + "type": "string" + }, + "server_id": { + "type": "string" + }, + "last_server_ping": { + "type": "string", + "format": "date-time" + }, + "error": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": {} + }, + "required": [ + "key", + "value" + ] + } + }, + "initial_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebsocketTriggerInitialMessage" + } + }, + "url_runnable_args": { + "$ref": "#/components/schemas/ScriptArgs" + } + }, + "required": [ + "path", + "edited_by", + "edited_at", + "script_path", + "url", + "extra_perms", + "is_flow", + "email", + "workspace_id", + "enabled", + "filters", + "initial_messages", + "url_runnable_args" + ] + }, + "NewWebsocketTrigger": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "url": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": {} + }, + "required": [ + "key", + "value" + ] + } + }, + "initial_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebsocketTriggerInitialMessage" + } + }, + "url_runnable_args": { + "$ref": "#/components/schemas/ScriptArgs" + } + }, + "required": [ + "path", + "script_path", + "url", + "is_flow", + "filters", + "initial_messages", + "url_runnable_args" + ] + }, + "EditWebsocketTrigger": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "path": { + "type": "string" + }, + "script_path": { + "type": "string" + }, + "is_flow": { + "type": "boolean" + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": {} + }, + "required": [ + "key", + "value" + ] + } + }, + "initial_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebsocketTriggerInitialMessage" + } + }, + "url_runnable_args": { + "$ref": "#/components/schemas/ScriptArgs" + } + }, + "required": [ + "path", + "script_path", + "url", + "is_flow", + "filters", + "initial_messages", + "url_runnable_args" + ] + }, + "WebsocketTriggerInitialMessage": { + "anyOf": [ + { + "type": "object", + "properties": { + "raw_message": { + "type": "string" + } + }, + "required": [ + "raw_message" + ] + }, + { + "type": "object", + "properties": { + "runnable_result": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "is_flow": { + "type": "boolean" + } + }, + "required": [ + "path", + "args", + "is_flow" + ] + } + }, + "required": [ + "runnable_result" + ] + } + ] + }, "Group": { "type": "object", "properties": { @@ -17817,6 +18743,9 @@ }, "username": { "type": "string" + }, + "operator_only": { + "type": "boolean" } }, "required": [ @@ -18796,6 +19725,66 @@ } } }, + "LogSearchHit": { + "type": "object", + "properties": { + "dancer": { + "type": "string" + } + } + }, + "AutoscalingEvent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "worker_group": { + "type": "string" + }, + "event_type": { + "type": "string" + }, + "desired_workers": { + "type": "integer" + }, + "reason": { + "type": "string" + }, + "applied_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CriticalAlert": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Unique identifier for the alert" + }, + "alert_type": { + "type": "string", + "description": "Type of alert (e.g., critical_error)" + }, + "message": { + "type": "string", + "description": "The message content of the alert" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Time when the alert was created" + }, + "acknowledged": { + "type": "boolean", + "nullable": true, + "description": "Acknowledgment status of the alert, can be true, false, or null if not set" + } + } + }, "StaticTransform": { "type": "object", "properties": { @@ -18901,6 +19890,9 @@ }, "custom_concurrency_key": { "type": "string" + }, + "is_trigger": { + "type": "boolean" } }, "required": [ @@ -18933,6 +19925,9 @@ }, "tag_override": { "type": "string" + }, + "is_trigger": { + "type": "boolean" } }, "required": [ diff --git a/backend/windmill-api/openapi-deref.yaml b/backend/windmill-api/openapi-deref.yaml index e2a0b00a85..94206795c2 100644 --- a/backend/windmill-api/openapi-deref.yaml +++ b/backend/windmill-api/openapi-deref.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 1.409.0 + version: 1.418.0 title: Windmill API contact: name: Windmill Team @@ -142,6 +142,7 @@ paths: - users.delete - users.update - users.login + - users.login_failure - users.logout - users.accept_invite - users.decline_invite @@ -153,6 +154,7 @@ paths: - users.impersonate - users.leave_workspace - oauth.login + - oauth.login_failure - oauth.signup - variables.create - variables.delete @@ -231,24 +233,24 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: &ref_109 + schema: &ref_118 type: string format: date-time - name: after description: filter on created after (exclusive) timestamp in: query - schema: &ref_110 + schema: &ref_119 type: string format: date-time - name: username description: filter on exact username of user in: query - schema: &ref_119 + schema: &ref_128 type: string - name: operation description: filter on exact or prefix name of operation in: query - schema: &ref_120 + schema: &ref_129 type: string - name: operations in: query @@ -263,12 +265,12 @@ paths: - name: resource description: filter on exact or prefix name of resource in: query - schema: &ref_121 + schema: &ref_130 type: string - name: action_kind description: filter on type of operation in: query - schema: &ref_122 + schema: &ref_131 type: string enum: - Create @@ -300,12 +302,12 @@ paths: application/json: schema: type: object - properties: &ref_136 + properties: &ref_143 email: type: string password: type: string - required: &ref_137 + required: &ref_144 - email - password responses: @@ -428,7 +430,7 @@ paths: application/json: schema: type: object - properties: &ref_138 + properties: &ref_145 is_admin: type: boolean operator: @@ -782,7 +784,7 @@ paths: application/json: schema: type: object - properties: &ref_172 + properties: &ref_183 email: type: string workspaces: @@ -800,7 +802,7 @@ paths: - id - name - username - required: &ref_173 + required: &ref_184 - email - workspaces /workspaces/list_as_superadmin: @@ -842,14 +844,14 @@ paths: application/json: schema: type: object - properties: &ref_174 + properties: &ref_185 id: type: string name: type: string username: type: string - required: &ref_175 + required: &ref_186 - id - name responses: @@ -1042,6 +1044,98 @@ paths: text/plain: schema: type: string + /settings/critical_alerts: + get: + summary: Get all critical alerts + operationId: getCriticalAlerts + tags: + - setting + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: The page number to retrieve (minimum value is 1) + - in: query + name: page_size + schema: + type: integer + default: 10 + maximum: 100 + description: Number of alerts per page (maximum is 100) + - in: query + name: acknowledged + schema: + type: boolean + nullable: true + description: >- + Filter by acknowledgment status; true for acknowledged, false for + unacknowledged, and omit for all alerts + responses: + '200': + description: Successfully retrieved all critical alerts + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_228 + id: + type: integer + description: Unique identifier for the alert + alert_type: + type: string + description: Type of alert (e.g., critical_error) + message: + type: string + description: The message content of the alert + created_at: + type: string + format: date-time + description: Time when the alert was created + acknowledged: + type: boolean + nullable: true + description: >- + Acknowledgment status of the alert, can be true, false, + or null if not set + /settings/critical_alerts/{id}/acknowledge: + post: + summary: Acknowledge a critical alert + operationId: acknowledgeCriticalAlert + tags: + - setting + parameters: + - in: path + name: id + required: true + schema: + type: integer + description: The ID of the critical alert to acknowledge + responses: + '200': + description: Successfully acknowledged the critical alert + content: + application/json: + schema: + type: string + example: Critical alert acknowledged + /settings/critical_alerts/acknowledge_all: + post: + summary: Acknowledge all unacknowledged critical alerts + operationId: acknowledgeAllCriticalAlerts + tags: + - setting + responses: + '200': + description: Successfully acknowledged all unacknowledged critical alerts. + content: + application/json: + schema: + type: string + example: All unacknowledged critical alerts acknowledged /settings/test_license_key: post: summary: test license key @@ -1197,12 +1291,12 @@ paths: type: array items: type: object - properties: &ref_214 + properties: &ref_221 name: type: string value: type: object - required: &ref_215 + required: &ref_222 - name - value /users/email: @@ -1361,6 +1455,8 @@ paths: type: string username: type: string + operator_only: + type: boolean required: &ref_13 - email - login_type @@ -1784,6 +1880,11 @@ paths: description: number of items to return for a given page (default 30, max 100) in: query schema: *ref_6 + - name: active_only + in: query + description: filter only active users + schema: + type: boolean responses: '200': description: user @@ -1932,7 +2033,7 @@ paths: type: array items: type: object - properties: &ref_198 + properties: &ref_205 script_path: type: string git_repo_resource_path: @@ -1957,7 +2058,7 @@ paths: - schedule - user - group - required: &ref_199 + required: &ref_206 - script_path - git_repo_resource_path deploy_ui: @@ -2639,6 +2740,32 @@ paths: text/plain: schema: type: number + /w/{workspace}/workspaces/used_triggers: + get: + summary: get used triggers + operationId: getUsedTriggers + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: object + properties: + http_routes_used: + type: boolean + websocket_used: + type: boolean + required: + - http_routes_used + - websocket_used /w/{workspace}/users/list: get: summary: list users @@ -2681,7 +2808,7 @@ paths: type: array items: type: object - properties: &ref_135 + properties: &ref_142 email: type: string executions: @@ -2742,7 +2869,7 @@ paths: application/json: schema: type: object - properties: &ref_139 + properties: &ref_146 label: type: string expiration: @@ -2774,7 +2901,7 @@ paths: application/json: schema: type: object - properties: &ref_140 + properties: &ref_147 label: type: string expiration: @@ -2784,7 +2911,7 @@ paths: type: string workspace_id: type: string - required: &ref_141 + required: &ref_148 - impersonate_email responses: '201': @@ -2909,7 +3036,7 @@ paths: application/json: schema: type: object - properties: &ref_144 + properties: &ref_151 path: type: string value: @@ -2925,7 +3052,7 @@ paths: expires_at: type: string format: date-time - required: &ref_145 + required: &ref_152 - path - value - is_secret @@ -3010,7 +3137,7 @@ paths: application/json: schema: type: object - properties: &ref_146 + properties: &ref_153 path: type: string value: @@ -3195,7 +3322,7 @@ paths: type: array items: type: object - properties: &ref_142 + properties: &ref_149 name: type: string value: @@ -3204,7 +3331,7 @@ paths: type: string is_custom: type: boolean - required: &ref_143 + required: &ref_150 - name - value - description @@ -3344,7 +3471,7 @@ paths: application/json: schema: type: object - properties: &ref_191 + properties: &ref_198 access_token: type: string expires_in: @@ -3355,7 +3482,7 @@ paths: type: array items: type: string - required: &ref_192 + required: &ref_199 - access_token /w/{workspace}/oauth/create_account: post: @@ -3560,7 +3687,7 @@ paths: application/json: schema: type: object - properties: &ref_153 + properties: &ref_160 path: type: string value: {} @@ -3568,7 +3695,7 @@ paths: type: string resource_type: type: string - required: &ref_154 + required: &ref_161 - path - value - resource_type @@ -3623,7 +3750,7 @@ paths: application/json: schema: type: object - properties: &ref_155 + properties: &ref_162 path: type: string description: @@ -3689,7 +3816,7 @@ paths: application/json: schema: type: object - properties: &ref_156 + properties: &ref_163 workspace_id: type: string path: @@ -3710,7 +3837,7 @@ paths: edited_at: type: string format: date-time - required: &ref_157 + required: &ref_164 - path - resource_type - is_oauth @@ -3826,7 +3953,7 @@ paths: type: array items: type: object - properties: &ref_158 + properties: &ref_165 workspace_id: type: string path: @@ -3857,7 +3984,7 @@ paths: edited_at: type: string format: date-time - required: &ref_159 + required: &ref_166 - path - resource_type - is_oauth @@ -3904,7 +4031,7 @@ paths: - name: name in: path required: true - schema: &ref_92 + schema: &ref_101 type: string responses: '200': @@ -4026,7 +4153,7 @@ paths: application/json: schema: type: object - properties: &ref_160 + properties: &ref_167 schema: {} description: type: string @@ -4254,14 +4381,14 @@ paths: properties: flow: type: object - properties: &ref_50 + properties: &ref_52 summary: type: string description: type: string value: type: object - properties: &ref_58 + properties: &ref_64 modules: type: array items: @@ -4270,32 +4397,32 @@ paths: id: type: string value: - oneOf: &ref_239 + oneOf: &ref_249 - type: object - properties: &ref_223 + properties: &ref_233 input_transforms: type: object additionalProperties: oneOf: &ref_29 - type: object - properties: &ref_219 + properties: &ref_229 value: {} type: type: string enum: - javascript - required: &ref_220 + required: &ref_230 - expr - type - type: object - properties: &ref_221 + properties: &ref_231 expr: type: string type: type: string enum: - javascript - required: &ref_222 + required: &ref_232 - expr - type discriminator: &ref_30 @@ -4338,13 +4465,15 @@ paths: type: number custom_concurrency_key: type: string - required: &ref_224 + is_trigger: + type: boolean + required: &ref_234 - type - content - language - input_transforms - type: object - properties: &ref_225 + properties: &ref_235 input_transforms: type: object additionalProperties: @@ -4360,12 +4489,14 @@ paths: - script tag_override: type: string - required: &ref_226 + is_trigger: + type: boolean + required: &ref_236 - type - path - input_transforms - type: object - properties: &ref_227 + properties: &ref_237 input_transforms: type: object additionalProperties: @@ -4377,12 +4508,12 @@ paths: type: string enum: - flow - required: &ref_228 + required: &ref_238 - type - path - input_transforms - type: object - properties: &ref_229 + properties: &ref_239 modules: type: array items: @@ -4404,13 +4535,13 @@ paths: type: boolean parallelism: type: integer - required: &ref_230 + required: &ref_240 - modules - iterator - skip_failures - type - type: object - properties: &ref_231 + properties: &ref_241 modules: type: array items: @@ -4427,12 +4558,12 @@ paths: type: boolean parallelism: type: integer - required: &ref_232 + required: &ref_242 - modules - skip_failures - type - type: object - properties: &ref_233 + properties: &ref_243 branches: type: array items: @@ -4463,12 +4594,12 @@ paths: type: string enum: - branchone - required: &ref_234 + required: &ref_244 - branches - default - type - type: object - properties: &ref_235 + properties: &ref_245 branches: type: array items: @@ -4493,20 +4624,20 @@ paths: - branchall parallel: type: boolean - required: &ref_236 + required: &ref_246 - branches - type - type: object - properties: &ref_237 + properties: &ref_247 type: type: string enum: - identity flow: type: boolean - required: &ref_238 + required: &ref_248 - type - discriminator: &ref_240 + discriminator: &ref_250 propertyName: type mapping: rawscript: '#/components/schemas/RawScript' @@ -4588,7 +4719,7 @@ paths: type: boolean retry: type: object - properties: &ref_87 + properties: &ref_93 constant: type: object properties: @@ -4634,11 +4765,11 @@ paths: type: number early_return: type: string - required: &ref_59 + required: &ref_65 - modules schema: type: object - required: &ref_51 + required: &ref_53 - summary - value /apps/hub/list: @@ -4945,12 +5076,12 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: &ref_48 + schema: &ref_50 type: boolean - name: created_by description: mask to filter exact matching user creator in: query - schema: &ref_49 + schema: &ref_51 type: string - name: path_start description: mask to filter matching starting path @@ -5611,7 +5742,7 @@ paths: application/json: schema: type: object - properties: &ref_53 + properties: &ref_57 primary_schedule: type: object properties: @@ -5625,6 +5756,8 @@ paths: type: number email_count: type: number + websocket_count: + type: number /w/{workspace}/scripts/list_tokens/{path}: get: summary: get tokens with script scope @@ -5672,7 +5805,7 @@ paths: content: application/json: schema: - allOf: &ref_126 + allOf: &ref_135 - type: object properties: *ref_40 required: *ref_41 @@ -5710,13 +5843,38 @@ paths: type: array items: type: object - properties: &ref_127 + properties: &ref_42 script_hash: type: string deployment_msg: type: string - required: &ref_128 + required: &ref_43 - script_hash + /w/{workspace}/scripts/get_latest_version/{path}: + get: + summary: get scripts's latest version (hash) + operationId: getScriptLatestVersion + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_33 + tags: + - script + responses: + '200': + description: Script version/hash + content: + application/json: + required: false + schema: + type: object + properties: *ref_42 + required: *ref_43 /w/{workspace}/scripts/history_update/h/{hash}/p/{path}: post: summary: update history of a script @@ -5791,7 +5949,7 @@ paths: - name: token in: path required: true - schema: &ref_115 + schema: &ref_124 type: string - name: path in: path @@ -5939,20 +6097,20 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: &ref_42 + schema: &ref_44 type: string format: uuid - name: tag description: Override the tag to use in: query - schema: &ref_44 + schema: &ref_46 type: string - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: &ref_45 + schema: &ref_47 type: string - name: job_id description: >- @@ -5961,7 +6119,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: &ref_43 + schema: &ref_45 type: string format: uuid - name: invisible_to_owner @@ -6005,7 +6163,7 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6013,7 +6171,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6022,14 +6180,14 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: &ref_46 + schema: &ref_48 type: string - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: &ref_47 + schema: &ref_49 type: string requestBody: description: script args @@ -6065,17 +6223,17 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_45 + schema: *ref_47 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6083,7 +6241,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6092,13 +6250,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_47 + schema: *ref_49 requestBody: description: script args required: true @@ -6132,17 +6290,17 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_45 + schema: *ref_47 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6150,7 +6308,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -6159,13 +6317,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_47 + schema: *ref_49 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -6173,7 +6331,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: &ref_86 + schema: &ref_92 type: string responses: '200': @@ -6204,13 +6362,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_47 + schema: *ref_49 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6218,7 +6376,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 requestBody: description: script args required: true @@ -6256,13 +6414,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_47 + schema: *ref_49 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -6270,7 +6428,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 requestBody: description: script args required: true @@ -6381,11 +6539,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: path_start description: mask to filter matching starting path in: query @@ -6439,12 +6597,12 @@ paths: type: array items: allOf: - - allOf: &ref_52 + - allOf: &ref_56 - type: object - properties: *ref_50 - required: *ref_51 + properties: *ref_52 + required: *ref_53 - type: object - properties: &ref_177 + properties: &ref_188 workspace_id: type: string path: @@ -6458,7 +6616,7 @@ paths: type: boolean extra_perms: type: object - additionalProperties: &ref_176 + additionalProperties: &ref_187 type: boolean starred: type: boolean @@ -6476,7 +6634,7 @@ paths: type: number visible_to_runner_only: type: boolean - required: &ref_178 + required: &ref_189 - path - edited_by - edited_at @@ -6512,7 +6670,7 @@ paths: type: array items: type: object - properties: &ref_189 + properties: &ref_54 id: type: integer created_at: @@ -6520,9 +6678,34 @@ paths: format: date-time deployment_msg: type: string - required: &ref_190 + required: &ref_55 - id - created_at + /w/{workspace}/flows/get_latest_version/{path}: + get: + summary: get flow's latest version + operationId: getFlowLatestVersion + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_33 + tags: + - flow + responses: + '200': + description: Flow version + content: + application/json: + required: false + schema: + type: object + properties: *ref_54 + required: *ref_55 /w/{workspace}/flows/get/v/{version}/p/{path}: get: summary: get flow version @@ -6550,7 +6733,7 @@ paths: content: application/json: schema: - allOf: *ref_52 + allOf: *ref_56 /w/{workspace}/flows/history_update/v/{version}/p/{path}: post: summary: update flow history @@ -6616,7 +6799,7 @@ paths: content: application/json: schema: - allOf: *ref_52 + allOf: *ref_56 /w/{workspace}/flows/get_triggers_count/{path}: get: summary: get triggers count of flow @@ -6639,7 +6822,7 @@ paths: application/json: schema: type: object - properties: *ref_53 + properties: *ref_57 /w/{workspace}/flows/list_tokens/{path}: get: summary: get tokens with flow scope @@ -6720,11 +6903,11 @@ paths: application/json: schema: allOf: - - allOf: *ref_52 + - allOf: *ref_56 - type: object properties: draft: - allOf: *ref_52 + allOf: *ref_56 /w/{workspace}/flows/exists/{path}: get: summary: exists flow by path @@ -6765,10 +6948,10 @@ paths: application/json: schema: allOf: - - allOf: &ref_54 + - allOf: &ref_58 - type: object - properties: *ref_50 - required: *ref_51 + properties: *ref_52 + required: *ref_53 - type: object properties: path: @@ -6822,7 +7005,7 @@ paths: application/json: schema: allOf: - - allOf: *ref_54 + - allOf: *ref_58 - type: object properties: deployment_message: @@ -6910,11 +7093,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: path_start description: mask to filter matching starting path in: query @@ -6941,7 +7124,7 @@ paths: type: array items: type: object - properties: &ref_184 + properties: &ref_195 workspace_id: type: string path: @@ -6959,7 +7142,7 @@ paths: edited_at: type: string format: date-time - required: &ref_185 + required: &ref_196 - workspace_id - path - summary @@ -7002,7 +7185,7 @@ paths: - name: version in: path required: true - schema: &ref_114 + schema: &ref_123 type: number - name: path in: path @@ -7064,11 +7247,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: path_start description: mask to filter matching starting path in: query @@ -7109,7 +7292,7 @@ paths: type: array items: type: object - properties: &ref_182 + properties: &ref_193 id: type: integer workspace_id: @@ -7135,7 +7318,7 @@ paths: - viewer - publisher - anonymous - required: &ref_183 + required: &ref_194 - id - workspace_id - path @@ -7170,7 +7353,7 @@ paths: type: string policy: type: object - properties: &ref_55 + properties: &ref_59 triggerables: type: object additionalProperties: @@ -7253,7 +7436,7 @@ paths: application/json: schema: type: object - properties: &ref_56 + properties: &ref_60 id: type: integer workspace_id: @@ -7275,7 +7458,7 @@ paths: type: object policy: type: object - properties: *ref_55 + properties: *ref_59 execution_mode: type: string enum: @@ -7286,7 +7469,7 @@ paths: type: object additionalProperties: type: boolean - required: &ref_57 + required: &ref_61 - id - workspace_id - path @@ -7319,10 +7502,10 @@ paths: content: application/json: schema: - allOf: &ref_186 + allOf: &ref_197 - type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_60 + required: *ref_61 - type: object properties: draft_only: @@ -7352,13 +7535,38 @@ paths: type: array items: type: object - properties: &ref_187 + properties: &ref_62 version: type: integer deployment_msg: type: string - required: &ref_188 + required: &ref_63 - version + /w/{workspace}/apps/get_latest_version/{path}: + get: + summary: get apps's latest version + operationId: getAppLatestVersion + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_33 + tags: + - app + responses: + '200': + description: App version + content: + application/json: + required: false + schema: + type: object + properties: *ref_62 + required: *ref_63 /w/{workspace}/apps/history_update/a/{id}/v/{version}: post: summary: update app history @@ -7377,7 +7585,7 @@ paths: - name: version in: path required: true - schema: &ref_116 + schema: &ref_125 type: integer requestBody: description: App deployment message @@ -7418,8 +7626,8 @@ paths: application/json: schema: type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_60 + required: *ref_61 /w/{workspace}/apps_u/public_resource/{path}: get: summary: get public resource @@ -7485,8 +7693,8 @@ paths: application/json: schema: type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_60 + required: *ref_61 /w/{workspace}/raw_apps/create: post: summary: create raw app @@ -7633,7 +7841,7 @@ paths: value: {} policy: type: object - properties: *ref_55 + properties: *ref_59 deployment_message: type: string responses: @@ -7741,11 +7949,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7753,7 +7961,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7762,7 +7970,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -7798,7 +8006,7 @@ paths: - name: id in: path required: true - schema: &ref_83 + schema: &ref_89 type: string format: uuid - name: step_id @@ -7831,11 +8039,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7843,7 +8051,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7852,7 +8060,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -7910,17 +8118,17 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_45 + schema: *ref_47 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7928,7 +8136,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7937,7 +8145,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -7977,7 +8185,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -7990,7 +8198,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 requestBody: description: preview required: true @@ -7998,7 +8206,7 @@ paths: application/json: schema: type: object - properties: &ref_147 + properties: &ref_154 content: type: string path: @@ -8037,7 +8245,7 @@ paths: type: boolean lock: type: string - required: &ref_148 + required: &ref_155 - args responses: '201': @@ -8075,11 +8283,11 @@ paths: application/json: schema: type: object - properties: &ref_149 + properties: &ref_156 args: type: object additionalProperties: *ref_16 - required: &ref_150 + required: &ref_157 - args responses: '201': @@ -8112,7 +8320,7 @@ paths: type: array items: type: object - properties: &ref_208 + properties: &ref_215 raw_code: type: string path: @@ -8136,7 +8344,7 @@ paths: - php - rust - ansible - required: &ref_209 + required: &ref_216 - raw_code - path - language @@ -8176,7 +8384,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -8189,7 +8397,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 requestBody: description: preview required: true @@ -8197,11 +8405,11 @@ paths: application/json: schema: type: object - properties: &ref_179 + properties: &ref_190 value: type: object - properties: *ref_58 - required: *ref_59 + properties: *ref_64 + required: *ref_65 path: type: string args: @@ -8211,7 +8419,7 @@ paths: type: string restarted_from: type: object - properties: &ref_181 + properties: &ref_192 flow_job_id: type: string format: uuid @@ -8219,7 +8427,7 @@ paths: type: string branch_or_iteration_n: type: integer - required: &ref_180 + required: &ref_191 - value - content - args @@ -8245,94 +8453,94 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: script_path_exact description: mask to filter exact matching path in: query - schema: &ref_62 + schema: &ref_68 type: string - name: script_path_start description: mask to filter matching starting path in: query - schema: &ref_63 + schema: &ref_69 type: string - name: schedule_path description: mask to filter by schedule path in: query - schema: &ref_64 + schema: &ref_70 type: string - name: script_hash description: mask to filter exact matching path in: query - schema: &ref_65 + schema: &ref_71 type: string - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: &ref_66 + schema: &ref_72 type: string format: date-time - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: &ref_67 + schema: &ref_73 type: string format: date-time - name: success description: filter on successful jobs in: query - schema: &ref_68 + schema: &ref_74 type: boolean - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: &ref_69 + schema: &ref_75 type: boolean - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: &ref_70 + schema: &ref_76 type: string - name: suspended description: filter on suspended jobs in: query - schema: &ref_71 + schema: &ref_77 type: boolean - name: running description: filter on running jobs in: query - schema: &ref_72 + schema: &ref_78 type: boolean - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: &ref_73 + schema: &ref_79 type: string - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: &ref_74 + schema: &ref_80 type: string - name: tag description: filter on jobs with a given tag/worker group in: query - schema: &ref_75 + schema: &ref_81 type: string - name: page description: which page to return (start at 1, default 1) @@ -8363,7 +8571,7 @@ paths: type: array items: type: object - properties: &ref_81 + properties: &ref_87 workspace_id: type: string id: @@ -8430,14 +8638,14 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: &ref_76 + properties: &ref_82 step: type: integer modules: type: array items: type: object - properties: &ref_60 + properties: &ref_66 type: type: string enum: @@ -8514,20 +8722,20 @@ paths: format: uuid skipped: type: boolean - required: &ref_61 + required: &ref_67 - type user_states: additionalProperties: true preprocessor_module: allOf: - type: object - properties: *ref_60 - required: *ref_61 + properties: *ref_66 + required: *ref_67 failure_module: allOf: - type: object - properties: *ref_60 - required: *ref_61 + properties: *ref_66 + required: *ref_67 - type: object properties: parent_module: @@ -8542,14 +8750,14 @@ paths: items: type: string format: uuid - required: &ref_77 + required: &ref_83 - step - modules - failure_module raw_flow: type: object - properties: *ref_58 - required: *ref_59 + properties: *ref_64 + required: *ref_65 is_flow_step: type: boolean language: @@ -8587,7 +8795,7 @@ paths: type: number suspend: type: number - required: &ref_82 + required: &ref_88 - id - running - canceled @@ -8666,79 +8874,79 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_62 + schema: *ref_68 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_63 + schema: *ref_69 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_64 + schema: *ref_70 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_65 + schema: *ref_71 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_66 + schema: *ref_72 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_67 + schema: *ref_73 - name: success description: filter on successful jobs in: query - schema: *ref_68 + schema: *ref_74 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_69 + schema: *ref_75 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_70 + schema: *ref_76 - name: suspended description: filter on suspended jobs in: query - schema: *ref_71 + schema: *ref_77 - name: running description: filter on running jobs in: query - schema: *ref_72 + schema: *ref_78 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_74 + schema: *ref_80 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_75 + schema: *ref_81 - name: page description: which page to return (start at 1, default 1) in: query @@ -8816,75 +9024,75 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: &ref_78 + schema: &ref_84 type: string - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_62 + schema: *ref_68 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_63 + schema: *ref_69 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_64 + schema: *ref_70 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_65 + schema: *ref_71 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_66 + schema: *ref_72 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_67 + schema: *ref_73 - name: success description: filter on successful jobs in: query - schema: *ref_68 + schema: *ref_74 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_70 + schema: *ref_76 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_74 + schema: *ref_80 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_75 + schema: *ref_81 - name: page description: which page to return (start at 1, default 1) in: query @@ -8922,7 +9130,7 @@ paths: type: array items: type: object - properties: &ref_79 + properties: &ref_85 workspace_id: type: string id: @@ -8988,12 +9196,12 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: *ref_76 - required: *ref_77 + properties: *ref_82 + required: *ref_83 raw_flow: type: object - properties: *ref_58 - required: *ref_59 + properties: *ref_64 + required: *ref_65 is_flow_step: type: boolean language: @@ -9035,7 +9243,7 @@ paths: type: number aggregate_wait_time_ms: type: number - required: &ref_80 + required: &ref_86 - id - created_by - duration_ms @@ -9064,54 +9272,54 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_78 + schema: *ref_84 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_62 + schema: *ref_68 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_63 + schema: *ref_69 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_64 + schema: *ref_70 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_65 + schema: *ref_71 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_66 + schema: *ref_72 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_67 + schema: *ref_73 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: &ref_117 + schema: &ref_126 type: string format: date-time - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: &ref_118 + schema: &ref_127 type: string format: date-time - name: created_or_started_before @@ -9119,23 +9327,23 @@ paths: filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: &ref_111 + schema: &ref_120 type: string format: date-time - name: running description: filter on running jobs in: query - schema: *ref_72 + schema: *ref_78 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_69 + schema: *ref_75 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: &ref_112 + schema: &ref_121 type: string format: date-time - name: created_or_started_after_completed_jobs @@ -9144,7 +9352,7 @@ paths: otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: &ref_113 + schema: &ref_122 type: string format: date-time - name: job_kinds @@ -9152,27 +9360,27 @@ paths: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_70 + schema: *ref_76 - name: suspended description: filter on suspended jobs in: query - schema: *ref_71 + schema: *ref_77 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_75 + schema: *ref_81 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_74 + schema: *ref_80 - name: page description: which page to return (start at 1, default 1) in: query @@ -9221,11 +9429,11 @@ paths: schema: type: array items: - oneOf: &ref_84 + oneOf: &ref_90 - allOf: - type: object - properties: *ref_79 - required: *ref_80 + properties: *ref_85 + required: *ref_86 - type: object properties: type: @@ -9234,15 +9442,15 @@ paths: - CompletedJob - allOf: - type: object - properties: *ref_81 - required: *ref_82 + properties: *ref_87 + required: *ref_88 - type: object properties: type: type: string enum: - QueuedJob - discriminator: &ref_85 + discriminator: &ref_91 propertyName: type /jobs/db_clock: get: @@ -9309,7 +9517,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: no_logs in: query schema: @@ -9320,8 +9528,8 @@ paths: content: application/json: schema: - oneOf: *ref_84 - discriminator: *ref_85 + oneOf: *ref_90 + discriminator: *ref_91 /w/{workspace}/jobs_u/get_root_job_id/{id}: get: summary: get root job id @@ -9336,7 +9544,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: get root job id @@ -9358,7 +9566,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: job details @@ -9380,7 +9588,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: job args @@ -9401,7 +9609,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: running in: query schema: @@ -9436,9 +9644,9 @@ paths: type: integer flow_status: type: object - additionalProperties: &ref_151 + additionalProperties: &ref_158 type: object - properties: &ref_152 + properties: &ref_159 scheduled_for: type: string format: date-time @@ -9485,7 +9693,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: flow debug info details @@ -9506,7 +9714,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: job details @@ -9514,8 +9722,8 @@ paths: application/json: schema: type: object - properties: *ref_79 - required: *ref_80 + properties: *ref_85 + required: *ref_86 /w/{workspace}/jobs_u/completed/get_result/{id}: get: summary: get completed job result @@ -9530,7 +9738,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: suspended_job in: query schema: @@ -9567,10 +9775,10 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: get_started in: query - schema: &ref_124 + schema: &ref_133 type: boolean responses: '200': @@ -9604,7 +9812,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: job details @@ -9612,8 +9820,8 @@ paths: application/json: schema: type: object - properties: *ref_79 - required: *ref_80 + properties: *ref_85 + required: *ref_86 /w/{workspace}/jobs_u/queue/cancel/{id}: post: summary: cancel queued or running job @@ -9628,7 +9836,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 requestBody: description: reason required: true @@ -9692,7 +9900,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 requestBody: description: reason required: true @@ -9724,7 +9932,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -9755,7 +9963,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -9797,7 +10005,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -9805,7 +10013,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_86 + schema: *ref_92 - name: resume_id in: path required: true @@ -9840,7 +10048,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -9882,7 +10090,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: key in: path required: true @@ -9914,7 +10122,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: key in: path required: true @@ -9940,7 +10148,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 requestBody: required: true content: @@ -9968,7 +10176,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -10003,7 +10211,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -10045,7 +10253,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 - name: resume_id in: path required: true @@ -10069,8 +10277,8 @@ paths: type: object properties: job: - oneOf: *ref_84 - discriminator: *ref_85 + oneOf: *ref_90 + discriminator: *ref_91 approvers: type: array items: @@ -10135,7 +10343,7 @@ paths: application/json: schema: type: object - properties: &ref_162 + properties: &ref_169 path: type: string schedule: @@ -10176,7 +10384,7 @@ paths: type: boolean retry: type: object - properties: *ref_87 + properties: *ref_93 no_flow_overlap: type: boolean summary: @@ -10186,7 +10394,7 @@ paths: paused_until: type: string format: date-time - required: &ref_163 + required: &ref_170 - path - schedule - timezone @@ -10222,7 +10430,7 @@ paths: application/json: schema: type: object - properties: &ref_164 + properties: &ref_171 schedule: type: string timezone: @@ -10255,7 +10463,7 @@ paths: type: boolean retry: type: object - properties: *ref_87 + properties: *ref_93 no_flow_overlap: type: boolean summary: @@ -10265,7 +10473,7 @@ paths: paused_until: type: string format: date-time - required: &ref_165 + required: &ref_172 - schedule - timezone - script_path @@ -10356,7 +10564,7 @@ paths: application/json: schema: type: object - properties: &ref_88 + properties: &ref_94 path: type: string edited_by: @@ -10410,7 +10618,7 @@ paths: type: boolean retry: type: object - properties: *ref_87 + properties: *ref_93 summary: type: string no_flow_overlap: @@ -10420,7 +10628,7 @@ paths: paused_until: type: string format: date-time - required: &ref_89 + required: &ref_95 - path - edited_by - edited_at @@ -10477,7 +10685,7 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 - name: path description: filter by path in: query @@ -10500,8 +10708,8 @@ paths: type: array items: type: object - properties: *ref_88 - required: *ref_89 + properties: *ref_94 + required: *ref_95 /w/{workspace}/schedules/list_with_jobs: get: summary: list schedules with last 20 jobs @@ -10529,10 +10737,10 @@ paths: schema: type: array items: - allOf: &ref_161 + allOf: &ref_168 - type: object - properties: *ref_88 - required: *ref_89 + properties: *ref_94 + required: *ref_95 - type: object properties: jobs: @@ -10611,7 +10819,7 @@ paths: application/json: schema: type: object - properties: &ref_166 + properties: &ref_173 path: type: string script_path: @@ -10632,7 +10840,7 @@ paths: type: boolean requires_auth: type: boolean - required: &ref_167 + required: &ref_174 - path - script_path - route_path @@ -10669,7 +10877,7 @@ paths: application/json: schema: type: object - properties: &ref_168 + properties: &ref_175 path: type: string script_path: @@ -10690,7 +10898,7 @@ paths: type: boolean requires_auth: type: boolean - required: &ref_169 + required: &ref_176 - path - script_path - is_flow @@ -10749,7 +10957,7 @@ paths: application/json: schema: type: object - properties: &ref_90 + properties: &ref_96 path: type: string edited_by: @@ -10783,7 +10991,7 @@ paths: type: boolean requires_auth: type: boolean - required: &ref_91 + required: &ref_97 - path - edited_by - edited_at @@ -10837,8 +11045,8 @@ paths: type: array items: type: object - properties: *ref_90 - required: *ref_91 + properties: *ref_96 + required: *ref_97 /w/{workspace}/http_triggers/exists/{path}: get: summary: does http trigger exists @@ -10901,24 +11109,362 @@ paths: application/json: schema: type: boolean - /w/{workspace}/http_triggers/used: - get: - summary: whether http triggers are used - operationId: used + /w/{workspace}/websocket_triggers/create: + post: + summary: create websocket trigger + operationId: createWebsocketTrigger tags: - - http_trigger + - websocket_trigger parameters: - name: workspace in: path required: true schema: *ref_0 + requestBody: + description: new websocket trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_177 + path: + type: string + script_path: + type: string + is_flow: + type: boolean + url: + type: string + enabled: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + initial_messages: + type: array + items: + anyOf: &ref_98 + - type: object + properties: + raw_message: + type: string + required: + - raw_message + - type: object + properties: + runnable_result: + type: object + properties: + path: + type: string + args: + type: object + additionalProperties: *ref_16 + is_flow: + type: boolean + required: + - path + - args + - is_flow + required: + - runnable_result + url_runnable_args: + type: object + additionalProperties: *ref_16 + required: &ref_178 + - path + - script_path + - url + - is_flow + - filters + - initial_messages + - url_runnable_args + responses: + '201': + description: websocket trigger created + content: + text/plain: + schema: + type: string + /w/{workspace}/websocket_triggers/update/{path}: + post: + summary: update websocket trigger + operationId: updateWebsocketTrigger + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_21 + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + type: object + properties: &ref_179 + url: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + initial_messages: + type: array + items: + anyOf: *ref_98 + url_runnable_args: + type: object + additionalProperties: *ref_16 + required: &ref_180 + - path + - script_path + - url + - is_flow + - filters + - initial_messages + - url_runnable_args responses: '200': - description: whether http triggers are used + description: websocket trigger updated + content: + text/plain: + schema: + type: string + /w/{workspace}/websocket_triggers/delete/{path}: + delete: + summary: delete websocket trigger + operationId: deleteWebsocketTrigger + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_21 + responses: + '200': + description: websocket trigger deleted + content: + text/plain: + schema: + type: string + /w/{workspace}/websocket_triggers/get/{path}: + get: + summary: get websocket trigger + operationId: getWebsocketTrigger + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_21 + responses: + '200': + description: websocket trigger deleted + content: + application/json: + schema: + type: object + properties: &ref_99 + path: + type: string + edited_by: + type: string + edited_at: + type: string + format: date-time + script_path: + type: string + url: + type: string + is_flow: + type: boolean + extra_perms: + type: object + additionalProperties: + type: boolean + email: + type: string + workspace_id: + type: string + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + initial_messages: + type: array + items: + anyOf: *ref_98 + url_runnable_args: + type: object + additionalProperties: *ref_16 + required: &ref_100 + - path + - edited_by + - edited_at + - script_path + - url + - extra_perms + - is_flow + - email + - workspace_id + - enabled + - filters + - initial_messages + - url_runnable_args + /w/{workspace}/websocket_triggers/list: + get: + summary: list websocket triggers + operationId: listWebsocketTriggers + tags: + - websocket_trigger + parameters: + - required: true + name: workspace + in: path + schema: *ref_0 + - name: page + description: which page to return (start at 1, default 1) + in: query + schema: *ref_5 + - name: per_page + description: number of items to return for a given page (default 30, max 100) + in: query + schema: *ref_6 + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + '200': + description: websocket trigger list + content: + application/json: + schema: + type: array + items: + type: object + properties: *ref_99 + required: *ref_100 + /w/{workspace}/websocket_triggers/exists/{path}: + get: + summary: does websocket trigger exists + operationId: existsWebsocketTrigger + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_21 + responses: + '200': + description: websocket trigger exists content: application/json: schema: type: boolean + /w/{workspace}/websocket_triggers/setenabled/{path}: + post: + summary: set enabled websocket trigger + operationId: setWebsocketTriggerEnabled + tags: + - websocket_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_21 + requestBody: + description: updated websocket trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + '200': + description: websocket trigger enabled set + content: + text/plain: + schema: + type: string /groups/list: get: summary: list instance groups @@ -10934,7 +11480,7 @@ paths: type: array items: type: object - properties: &ref_93 + properties: &ref_102 name: type: string summary: @@ -10943,7 +11489,7 @@ paths: type: array items: type: string - required: &ref_94 + required: &ref_103 - name /groups/get/{name}: get: @@ -10955,7 +11501,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: instance group @@ -10963,8 +11509,8 @@ paths: application/json: schema: type: object - properties: *ref_93 - required: *ref_94 + properties: *ref_102 + required: *ref_103 /groups/create: post: summary: create instance group @@ -11002,7 +11548,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: update instance group required: true @@ -11032,7 +11578,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: instance group deleted @@ -11050,7 +11596,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: user to add to instance group required: true @@ -11080,7 +11626,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: user to remove from instance group required: true @@ -11115,7 +11661,7 @@ paths: type: array items: type: object - properties: &ref_95 + properties: &ref_104 name: type: string summary: @@ -11130,7 +11676,7 @@ paths: type: string external_id: type: string - required: &ref_96 + required: &ref_105 - name /groups/overwrite: post: @@ -11147,8 +11693,8 @@ paths: type: array items: type: object - properties: *ref_95 - required: *ref_96 + properties: *ref_104 + required: *ref_105 responses: '200': description: success message @@ -11184,7 +11730,7 @@ paths: type: array items: type: object - properties: &ref_97 + properties: &ref_106 name: type: string summary: @@ -11197,7 +11743,7 @@ paths: type: object additionalProperties: type: boolean - required: &ref_98 + required: &ref_107 - name /w/{workspace}/groups/listnames: get: @@ -11270,7 +11816,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: updated group required: true @@ -11302,7 +11848,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: group deleted @@ -11324,7 +11870,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: group @@ -11332,8 +11878,8 @@ paths: application/json: schema: type: object - properties: *ref_97 - required: *ref_98 + properties: *ref_106 + required: *ref_107 /w/{workspace}/groups/adduser/{name}: post: summary: add user to group @@ -11348,7 +11894,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: added user to group required: true @@ -11380,7 +11926,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: added user to group required: true @@ -11426,7 +11972,7 @@ paths: type: array items: type: object - properties: &ref_99 + properties: &ref_108 name: type: string owners: @@ -11444,7 +11990,7 @@ paths: edited_at: type: string format: date-time - required: &ref_100 + required: &ref_109 - name - owners - extra_perms @@ -11526,7 +12072,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: update folder required: true @@ -11565,7 +12111,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: folder deleted @@ -11587,7 +12133,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: folder @@ -11595,8 +12141,8 @@ paths: application/json: schema: type: object - properties: *ref_99 - required: *ref_100 + properties: *ref_108 + required: *ref_109 /w/{workspace}/folders/getusage/{name}: get: summary: get folder usage @@ -11611,7 +12157,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: folder @@ -11653,7 +12199,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: owner user to folder required: true @@ -11687,7 +12233,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: added owner to folder required: true @@ -11741,7 +12287,7 @@ paths: type: array items: type: object - properties: &ref_170 + properties: &ref_181 worker: type: string worker_instance: @@ -11783,7 +12329,7 @@ paths: type: number wm_memory_usage: type: number - required: &ref_171 + required: &ref_182 - worker - worker_instance - ping_at @@ -11876,7 +12422,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: a config @@ -11893,7 +12439,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 requestBody: description: worker group required: true @@ -11916,7 +12462,7 @@ paths: - name: name in: path required: true - schema: *ref_92 + schema: *ref_101 responses: '200': description: Delete config @@ -11939,13 +12485,49 @@ paths: type: array items: type: object - properties: &ref_216 + properties: &ref_223 name: type: string config: type: object - required: &ref_217 + required: &ref_224 - name + /configs/list_autoscaling_events/{worker_group}: + get: + summary: List autoscaling events + operationId: listAutoscalingEvents + tags: + - config + parameters: + - name: worker_group + in: path + required: true + schema: + type: string + responses: + '200': + description: List of autoscaling events + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_227 + id: + type: integer + format: int64 + worker_group: + type: string + event_type: + type: string + desired_workers: + type: integer + reason: + type: string + applied_at: + type: string + format: date-time /w/{workspace}/acls/get/{kind}/{path}: get: summary: get granular acls @@ -11977,6 +12559,7 @@ paths: - app - raw_app - http_trigger + - websocket_trigger responses: '200': description: acls @@ -12017,6 +12600,7 @@ paths: - app - raw_app - http_trigger + - websocket_trigger requestBody: description: acl to add required: true @@ -12069,6 +12653,7 @@ paths: - app - raw_app - http_trigger + - websocket_trigger requestBody: description: acl to add required: true @@ -12217,13 +12802,13 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: &ref_101 + schema: &ref_110 type: string - name: runnable_type in: query - schema: &ref_102 + schema: &ref_111 type: string - enum: &ref_133 + enum: &ref_140 - ScriptHash - ScriptPath - FlowPath @@ -12244,7 +12829,7 @@ paths: type: array items: type: object - properties: &ref_103 + properties: &ref_112 id: type: string name: @@ -12258,7 +12843,7 @@ paths: type: boolean success: type: boolean - required: &ref_104 + required: &ref_113 - id - name - args @@ -12308,10 +12893,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_101 + schema: *ref_110 - name: runnable_type in: query - schema: *ref_102 + schema: *ref_111 - name: page description: which page to return (start at 1, default 1) in: query @@ -12329,8 +12914,8 @@ paths: type: array items: type: object - properties: *ref_103 - required: *ref_104 + properties: *ref_112 + required: *ref_113 /w/{workspace}/inputs/create: post: summary: Create an Input for future use in a script or flow @@ -12344,10 +12929,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_101 + schema: *ref_110 - name: runnable_type in: query - schema: *ref_102 + schema: *ref_111 requestBody: description: Input required: true @@ -12355,12 +12940,12 @@ paths: application/json: schema: type: object - properties: &ref_129 + properties: &ref_136 name: type: string args: type: object - required: &ref_130 + required: &ref_137 - name - args - created_by @@ -12390,14 +12975,14 @@ paths: application/json: schema: type: object - properties: &ref_131 + properties: &ref_138 id: type: string name: type: string is_public: type: boolean - required: &ref_132 + required: &ref_139 - id - name - is_public @@ -12423,7 +13008,7 @@ paths: - name: input in: path required: true - schema: &ref_123 + schema: &ref_132 type: string responses: '200': @@ -12456,7 +13041,7 @@ paths: properties: s3_resource: type: object - properties: &ref_105 + properties: &ref_114 bucket: type: string region: @@ -12471,7 +13056,7 @@ paths: type: string pathStyle: type: boolean - required: &ref_106 + required: &ref_115 - bucket - region - endPoint @@ -12547,8 +13132,8 @@ paths: properties: s3_resource: type: object - properties: *ref_105 - required: *ref_106 + properties: *ref_114 + required: *ref_115 responses: '200': description: Connection settings @@ -12569,10 +13154,10 @@ paths: type: boolean client_kwargs: type: object - properties: &ref_107 + properties: &ref_116 region_name: type: string - required: &ref_108 + required: &ref_117 - region_name required: - endpoint_url @@ -12627,8 +13212,8 @@ paths: type: boolean client_kwargs: type: object - properties: *ref_107 - required: *ref_108 + properties: *ref_116 + required: *ref_117 required: - endpoint_url - use_ssl @@ -12686,8 +13271,8 @@ paths: application/json: schema: type: object - properties: *ref_105 - required: *ref_106 + properties: *ref_114 + required: *ref_115 /w/{workspace}/job_helpers/test_connection: get: summary: Test connection to the workspace object storage @@ -12751,10 +13336,10 @@ paths: type: array items: type: object - properties: &ref_193 + properties: &ref_200 s3: type: string - required: &ref_194 + required: &ref_201 - s3 restricted_access: type: boolean @@ -12787,7 +13372,7 @@ paths: application/json: schema: type: object - properties: &ref_195 + properties: &ref_202 mime_type: type: string size_in_bytes: @@ -12851,7 +13436,7 @@ paths: application/json: schema: type: object - properties: &ref_196 + properties: &ref_203 msg: type: string content: @@ -12863,7 +13448,7 @@ paths: - Csv - Parquet - Unknown - required: &ref_197 + required: &ref_204 - content_type /w/{workspace}/job_helpers/load_parquet_preview/{path}: get: @@ -13201,7 +13786,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 requestBody: description: parameters for statistics retrieval required: true @@ -13230,46 +13815,46 @@ paths: type: array items: type: object - properties: &ref_200 + properties: &ref_207 id: type: string name: type: string - required: &ref_201 + required: &ref_208 - id scalar_metrics: type: array items: type: object - properties: &ref_202 + properties: &ref_209 metric_id: type: string value: type: number - required: &ref_203 + required: &ref_210 - id - value timeseries_metrics: type: array items: type: object - properties: &ref_204 + properties: &ref_211 metric_id: type: string values: type: array items: type: object - properties: &ref_206 + properties: &ref_213 timestamp: type: string format: date-time value: type: number - required: &ref_207 + required: &ref_214 - timestamp - value - required: &ref_205 + required: &ref_212 - id - values /w/{workspace}/job_metrics/set_progress/{id}: @@ -13286,7 +13871,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 requestBody: description: parameters for statistics retrieval required: true @@ -13320,7 +13905,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: job progress between 0 and 99 @@ -13338,11 +13923,11 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_109 + schema: *ref_118 - name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_110 + schema: *ref_119 - name: with_error in: query required: false @@ -13414,12 +13999,12 @@ paths: type: array items: type: object - properties: &ref_210 + properties: &ref_217 concurrency_key: type: string total_running: type: number - required: &ref_211 + required: &ref_218 - concurrency_key - total_running /concurrency_groups/prune/{concurrency_id}: @@ -13432,7 +14017,7 @@ paths: - name: concurrency_id in: path required: true - schema: &ref_125 + schema: &ref_134 type: string responses: '200': @@ -13452,7 +14037,7 @@ paths: - name: id in: path required: true - schema: *ref_83 + schema: *ref_89 responses: '200': description: concurrency key for given job @@ -13485,93 +14070,93 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_78 + schema: *ref_84 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_62 + schema: *ref_68 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_63 + schema: *ref_69 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_64 + schema: *ref_70 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_65 + schema: *ref_71 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_66 + schema: *ref_72 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_67 + schema: *ref_73 - name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_111 + schema: *ref_120 - name: running description: filter on running jobs in: query - schema: *ref_72 + schema: *ref_78 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_69 + schema: *ref_75 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_112 + schema: *ref_121 - name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_113 + schema: *ref_122 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_70 + schema: *ref_76 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_75 + schema: *ref_81 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_74 + schema: *ref_80 - name: page description: which page to return (start at 1, default 1) in: query @@ -13619,17 +14204,17 @@ paths: application/json: schema: type: object - properties: &ref_212 + properties: &ref_219 jobs: type: array items: - oneOf: *ref_84 - discriminator: *ref_85 + oneOf: *ref_90 + discriminator: *ref_91 obscured_jobs: type: array items: type: object - properties: &ref_134 + properties: &ref_141 typ: type: string started_at: @@ -13642,7 +14227,7 @@ paths: Obscured jobs omitted for security because of too specific filtering type: boolean - required: &ref_213 + required: &ref_220 - jobs - obscured_jobs /srch/w/{workspace}/index/search/job: @@ -13684,9 +14269,118 @@ paths: type: array items: type: object - properties: &ref_218 + properties: &ref_225 dancer: type: string + /srch/index/search/service_logs: + get: + summary: Search through service logs with a string query + operationId: searchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: mode + in: query + required: true + schema: + type: string + - name: worker_group + in: query + required: false + schema: + type: string + - name: hostname + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + '200': + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: >- + a list of the terms that couldn't be parsed (and thus + ignored) + type: array + items: + type: string + hits: + description: log files that matched the query + type: array + items: + type: object + properties: &ref_226 + dancer: + type: string + /srch/index/search/count_service_logs: + get: + summary: Search and count the log line hits on every provided host + operationId: countSearchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: hosts + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + '200': + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: >- + a list of the terms that couldn't be parsed (and thus + ignored) + type: array + items: + type: string + count_per_host: + description: count of log lines that matched the query per hostname + type: object components: securitySchemes: bearerAuth: @@ -13711,12 +14405,12 @@ components: name: version in: path required: true - schema: *ref_114 + schema: *ref_123 Token: name: token in: path required: true - schema: *ref_115 + schema: *ref_124 AccountId: name: id in: path @@ -13741,7 +14435,7 @@ components: name: id in: path required: true - schema: *ref_83 + schema: *ref_89 Path: name: path in: path @@ -13756,12 +14450,12 @@ components: name: version in: path required: true - schema: *ref_116 + schema: *ref_125 Name: name: name in: path required: true - schema: *ref_92 + schema: *ref_101 Page: name: page description: which page to return (start at 1, default 1) @@ -13776,12 +14470,12 @@ components: name: order_desc description: order by desc order (default true) in: query - schema: *ref_48 + schema: *ref_50 CreatedBy: name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_49 + schema: *ref_51 Label: name: label description: >- @@ -13789,26 +14483,26 @@ components: with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_78 + schema: *ref_84 ParentJob: name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_42 + schema: *ref_44 WorkerTag: name: tag description: Override the tag to use in: query - schema: *ref_44 + schema: *ref_46 CacheTtl: name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_45 + schema: *ref_47 NewJobId: name: job_id description: >- @@ -13816,7 +14510,7 @@ components: randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_43 + schema: *ref_45 IncludeHeader: name: include_header description: > @@ -13826,14 +14520,14 @@ components: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_46 + schema: *ref_48 QueueLimit: name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_47 + schema: *ref_49 Payload: name: payload description: > @@ -13842,162 +14536,162 @@ components: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_86 + schema: *ref_92 ScriptStartPath: name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_63 + schema: *ref_69 SchedulePath: name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_64 + schema: *ref_70 ScriptExactPath: name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_62 + schema: *ref_68 ScriptExactHash: name: script_hash description: mask to filter exact matching path in: query - schema: *ref_65 + schema: *ref_71 CreatedBefore: name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_117 + schema: *ref_126 CreatedAfter: name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_118 + schema: *ref_127 StartedBefore: name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_66 + schema: *ref_72 StartedAfter: name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_67 + schema: *ref_73 Before: name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_109 + schema: *ref_118 CreatedOrStartedAfter: name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_112 + schema: *ref_121 CreatedOrStartedAfterCompletedJob: name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_113 + schema: *ref_122 CreatedOrStartedBefore: name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_111 + schema: *ref_120 Success: name: success description: filter on successful jobs in: query - schema: *ref_68 + schema: *ref_74 ScheduledForBeforeNow: name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_69 + schema: *ref_75 Suspended: name: suspended description: filter on suspended jobs in: query - schema: *ref_71 + schema: *ref_77 Running: name: running description: filter on running jobs in: query - schema: *ref_72 + schema: *ref_78 ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_73 + schema: *ref_79 Tag: name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_75 + schema: *ref_81 ResultFilter: name: result description: filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_74 + schema: *ref_80 After: name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_110 + schema: *ref_119 Username: name: username description: filter on exact username of user in: query - schema: *ref_119 + schema: *ref_128 Operation: name: operation description: filter on exact or prefix name of operation in: query - schema: *ref_120 + schema: *ref_129 ResourceName: name: resource description: filter on exact or prefix name of resource in: query - schema: *ref_121 + schema: *ref_130 ActionKind: name: action_kind description: filter on type of operation in: query - schema: *ref_122 + schema: *ref_131 JobKinds: name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_70 + schema: *ref_76 RunnableId: name: runnable_id in: query - schema: *ref_101 + schema: *ref_110 RunnableTypeQuery: name: runnable_type in: query - schema: *ref_102 + schema: *ref_111 InputId: name: input in: path required: true - schema: *ref_123 + schema: *ref_132 GetStarted: name: get_started in: query - schema: *ref_124 + schema: *ref_133 ConcurrencyId: name: concurrency_id in: path required: true - schema: *ref_125 + schema: *ref_134 schemas: Script: type: object @@ -14008,83 +14702,83 @@ components: properties: *ref_40 required: *ref_41 NewScriptWithDraft: - allOf: *ref_126 + allOf: *ref_135 ScriptHistory: type: object - properties: *ref_127 - required: *ref_128 + properties: *ref_42 + required: *ref_43 ScriptArgs: type: object additionalProperties: *ref_16 Input: type: object - properties: *ref_103 - required: *ref_104 + properties: *ref_112 + required: *ref_113 CreateInput: type: object - properties: *ref_129 - required: *ref_130 + properties: *ref_136 + required: *ref_137 UpdateInput: type: object - properties: *ref_131 - required: *ref_132 + properties: *ref_138 + required: *ref_139 RunnableType: type: string - enum: *ref_133 + enum: *ref_140 QueuedJob: type: object - properties: *ref_81 - required: *ref_82 + properties: *ref_87 + required: *ref_88 CompletedJob: type: object - properties: *ref_79 - required: *ref_80 + properties: *ref_85 + required: *ref_86 ObscuredJob: type: object - properties: *ref_134 + properties: *ref_141 Job: - oneOf: *ref_84 - discriminator: *ref_85 + oneOf: *ref_90 + discriminator: *ref_91 User: type: object properties: *ref_10 required: *ref_11 UserUsage: type: object - properties: *ref_135 + properties: *ref_142 Login: type: object - properties: *ref_136 - required: *ref_137 + properties: *ref_143 + required: *ref_144 EditWorkspaceUser: type: object - properties: *ref_138 + properties: *ref_145 TruncatedToken: type: object properties: *ref_38 required: *ref_39 NewToken: type: object - properties: *ref_139 + properties: *ref_146 NewTokenImpersonate: type: object - properties: *ref_140 - required: *ref_141 + properties: *ref_147 + required: *ref_148 ListableVariable: type: object properties: *ref_22 required: *ref_23 ContextualVariable: type: object - properties: *ref_142 - required: *ref_143 + properties: *ref_149 + required: *ref_150 CreateVariable: type: object - properties: *ref_144 - required: *ref_145 + properties: *ref_151 + required: *ref_152 EditVariable: type: object - properties: *ref_146 + properties: *ref_153 AuditLog: type: object properties: *ref_1 @@ -14216,93 +14910,107 @@ components: - has_preprocessor Preview: type: object - properties: *ref_147 - required: *ref_148 + properties: *ref_154 + required: *ref_155 WorkflowTask: - type: object - properties: *ref_149 - required: *ref_150 - WorkflowStatusRecord: - type: object - additionalProperties: *ref_151 - WorkflowStatus: - type: object - properties: *ref_152 - CreateResource: - type: object - properties: *ref_153 - required: *ref_154 - EditResource: - type: object - properties: *ref_155 - Resource: type: object properties: *ref_156 required: *ref_157 + WorkflowStatusRecord: + type: object + additionalProperties: *ref_158 + WorkflowStatus: + type: object + properties: *ref_159 + CreateResource: + type: object + properties: *ref_160 + required: *ref_161 + EditResource: + type: object + properties: *ref_162 + Resource: + type: object + properties: *ref_163 + required: *ref_164 ListableResource: type: object - properties: *ref_158 - required: *ref_159 + properties: *ref_165 + required: *ref_166 ResourceType: type: object properties: *ref_26 required: *ref_27 EditResourceType: type: object - properties: *ref_160 + properties: *ref_167 Schedule: type: object - properties: *ref_88 - required: *ref_89 + properties: *ref_94 + required: *ref_95 ScheduleWJobs: - allOf: *ref_161 + allOf: *ref_168 NewSchedule: type: object - properties: *ref_162 - required: *ref_163 + properties: *ref_169 + required: *ref_170 EditSchedule: type: object - properties: *ref_164 - required: *ref_165 + properties: *ref_171 + required: *ref_172 HttpTrigger: type: object - properties: *ref_90 - required: *ref_91 + properties: *ref_96 + required: *ref_97 NewHttpTrigger: type: object - properties: *ref_166 - required: *ref_167 + properties: *ref_173 + required: *ref_174 EditHttpTrigger: type: object - properties: *ref_168 - required: *ref_169 + properties: *ref_175 + required: *ref_176 TriggersCount: type: object - properties: *ref_53 - Group: - type: object - properties: *ref_97 - required: *ref_98 - InstanceGroup: - type: object - properties: *ref_93 - required: *ref_94 - Folder: + properties: *ref_57 + WebsocketTrigger: type: object properties: *ref_99 required: *ref_100 + NewWebsocketTrigger: + type: object + properties: *ref_177 + required: *ref_178 + EditWebsocketTrigger: + type: object + properties: *ref_179 + required: *ref_180 + WebsocketTriggerInitialMessage: + anyOf: *ref_98 + Group: + type: object + properties: *ref_106 + required: *ref_107 + InstanceGroup: + type: object + properties: *ref_102 + required: *ref_103 + Folder: + type: object + properties: *ref_108 + required: *ref_109 WorkerPing: type: object - properties: *ref_170 - required: *ref_171 + properties: *ref_181 + required: *ref_182 UserWorkspaceList: type: object - properties: *ref_172 - required: *ref_173 + properties: *ref_183 + required: *ref_184 CreateWorkspace: type: object - properties: *ref_174 - required: *ref_175 + properties: *ref_185 + required: *ref_186 Workspace: type: object properties: *ref_7 @@ -14316,48 +15024,48 @@ components: properties: *ref_12 required: *ref_13 Flow: - allOf: *ref_52 + allOf: *ref_56 ExtraPerms: type: object - additionalProperties: *ref_176 + additionalProperties: *ref_187 FlowMetadata: type: object - properties: *ref_177 - required: *ref_178 + properties: *ref_188 + required: *ref_189 OpenFlowWPath: - allOf: *ref_54 + allOf: *ref_58 FlowPreview: type: object - properties: *ref_179 - required: *ref_180 + properties: *ref_190 + required: *ref_191 RestartedFrom: type: object - properties: *ref_181 + properties: *ref_192 Policy: type: object - properties: *ref_55 + properties: *ref_59 ListableApp: type: object - properties: *ref_182 - required: *ref_183 + properties: *ref_193 + required: *ref_194 ListableRawApp: type: object - properties: *ref_184 - required: *ref_185 + properties: *ref_195 + required: *ref_196 AppWithLastVersion: type: object - properties: *ref_56 - required: *ref_57 + properties: *ref_60 + required: *ref_61 AppWithLastVersionWDraft: - allOf: *ref_186 + allOf: *ref_197 AppHistory: type: object - properties: *ref_187 - required: *ref_188 + properties: *ref_62 + required: *ref_63 FlowVersion: type: object - properties: *ref_189 - required: *ref_190 + properties: *ref_54 + required: *ref_55 SlackToken: type: object properties: @@ -14379,33 +15087,33 @@ components: - bot TokenResponse: type: object - properties: *ref_191 - required: *ref_192 + properties: *ref_198 + required: *ref_199 HubScriptKind: name: kind schema: *ref_34 PolarsClientKwargs: type: object - properties: *ref_107 - required: *ref_108 + properties: *ref_116 + required: *ref_117 LargeFileStorage: type: object properties: *ref_17 WindmillLargeFile: type: object - properties: *ref_193 - required: *ref_194 + properties: *ref_200 + required: *ref_201 WindmillFileMetadata: type: object - properties: *ref_195 + properties: *ref_202 WindmillFilePreview: type: object - properties: *ref_196 - required: *ref_197 + properties: *ref_203 + required: *ref_204 S3Resource: type: object - properties: *ref_105 - required: *ref_106 + properties: *ref_114 + required: *ref_115 WorkspaceGitSyncSettings: type: object properties: *ref_18 @@ -14417,8 +15125,8 @@ components: properties: *ref_20 GitRepositorySettings: type: object - properties: *ref_198 - required: *ref_199 + properties: *ref_205 + required: *ref_206 UploadFilePart: type: object properties: @@ -14431,117 +15139,126 @@ components: - tag MetricMetadata: type: object - properties: *ref_200 - required: *ref_201 + properties: *ref_207 + required: *ref_208 ScalarMetric: type: object - properties: *ref_202 - required: *ref_203 + properties: *ref_209 + required: *ref_210 TimeseriesMetric: type: object - properties: *ref_204 - required: *ref_205 + properties: *ref_211 + required: *ref_212 MetricDataPoint: type: object - properties: *ref_206 - required: *ref_207 + properties: *ref_213 + required: *ref_214 RawScriptForDependencies: type: object - properties: *ref_208 - required: *ref_209 + properties: *ref_215 + required: *ref_216 ConcurrencyGroup: type: object - properties: *ref_210 - required: *ref_211 + properties: *ref_217 + required: *ref_218 ExtendedJobs: type: object - properties: *ref_212 - required: *ref_213 + properties: *ref_219 + required: *ref_220 ExportedUser: type: object properties: *ref_3 required: *ref_4 GlobalSetting: - type: object - properties: *ref_214 - required: *ref_215 - Config: - type: object - properties: *ref_216 - required: *ref_217 - ExportedInstanceGroup: - type: object - properties: *ref_95 - required: *ref_96 - JobSearchHit: - type: object - properties: *ref_218 - StaticTransform: - type: object - properties: *ref_219 - required: *ref_220 - JavascriptTransform: type: object properties: *ref_221 required: *ref_222 + Config: + type: object + properties: *ref_223 + required: *ref_224 + ExportedInstanceGroup: + type: object + properties: *ref_104 + required: *ref_105 + JobSearchHit: + type: object + properties: *ref_225 + LogSearchHit: + type: object + properties: *ref_226 + AutoscalingEvent: + type: object + properties: *ref_227 + CriticalAlert: + type: object + properties: *ref_228 + StaticTransform: + type: object + properties: *ref_229 + required: *ref_230 + JavascriptTransform: + type: object + properties: *ref_231 + required: *ref_232 InputTransform: oneOf: *ref_29 discriminator: *ref_30 RawScript: type: object - properties: *ref_223 - required: *ref_224 + properties: *ref_233 + required: *ref_234 PathScript: type: object - properties: *ref_225 - required: *ref_226 + properties: *ref_235 + required: *ref_236 PathFlow: type: object - properties: *ref_227 - required: *ref_228 + properties: *ref_237 + required: *ref_238 FlowModule: type: object properties: *ref_31 required: *ref_32 ForloopFlow: type: object - properties: *ref_229 - required: *ref_230 + properties: *ref_239 + required: *ref_240 WhileloopFlow: type: object - properties: *ref_231 - required: *ref_232 + properties: *ref_241 + required: *ref_242 BranchOne: type: object - properties: *ref_233 - required: *ref_234 + properties: *ref_243 + required: *ref_244 BranchAll: type: object - properties: *ref_235 - required: *ref_236 + properties: *ref_245 + required: *ref_246 Identity: type: object - properties: *ref_237 - required: *ref_238 + properties: *ref_247 + required: *ref_248 FlowModuleValue: - oneOf: *ref_239 - discriminator: *ref_240 + oneOf: *ref_249 + discriminator: *ref_250 Retry: type: object - properties: *ref_87 + properties: *ref_93 FlowValue: type: object - properties: *ref_58 - required: *ref_59 + properties: *ref_64 + required: *ref_65 OpenFlow: type: object - properties: *ref_50 - required: *ref_51 + properties: *ref_52 + required: *ref_53 FlowStatusModule: type: object - properties: *ref_60 - required: *ref_61 + properties: *ref_66 + required: *ref_67 FlowStatus: type: object - properties: *ref_76 - required: *ref_77 + properties: *ref_82 + required: *ref_83 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index eebb8fd0a9..b63b6c0f34 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.416.2 + version: 1.423.1 title: Windmill API contact: @@ -190,7 +190,7 @@ paths: schema: type: string - /w/{workspace}/users/{username}: + /w/{workspace}/users/get/{username}: get: summary: get user (require admin privilege) operationId: getUser @@ -724,6 +724,8 @@ paths: type: string tls_implicit: type: boolean + disable_tls: + type: boolean required: - host - username @@ -731,6 +733,7 @@ paths: - port - from - tls_implicit + - disable_tls required: - to - smtp @@ -770,6 +773,78 @@ paths: schema: type: string + /settings/critical_alerts: + get: + summary: Get all critical alerts + operationId: getCriticalAlerts + tags: + - setting + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: The page number to retrieve (minimum value is 1) + - in: query + name: page_size + schema: + type: integer + default: 10 + maximum: 100 + description: Number of alerts per page (maximum is 100) + - in: query + name: acknowledged + schema: + type: boolean + nullable: true + description: Filter by acknowledgment status; true for acknowledged, false for unacknowledged, and omit for all alerts + responses: + "200": + description: Successfully retrieved all critical alerts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CriticalAlert' + + /settings/critical_alerts/{id}/acknowledge: + post: + summary: Acknowledge a critical alert + operationId: acknowledgeCriticalAlert + tags: + - setting + parameters: + - in: path + name: id + required: true + schema: + type: integer + description: The ID of the critical alert to acknowledge + responses: + "200": + description: Successfully acknowledged the critical alert + content: + application/json: + schema: + type: string + example: "Critical alert acknowledged" + + /settings/critical_alerts/acknowledge_all: + post: + summary: Acknowledge all unacknowledged critical alerts + operationId: acknowledgeAllCriticalAlerts + tags: + - setting + responses: + "200": + description: Successfully acknowledged all unacknowledged critical alerts. + content: + application/json: + schema: + type: string + example: "All unacknowledged critical alerts acknowledged" /settings/test_license_key: post: @@ -9143,6 +9218,14 @@ paths: in: query schema: type: string + - name: content_type + in: query + schema: + type: string + - name: content_disposition + in: query + schema: + type: string requestBody: description: File content required: true @@ -9546,6 +9629,109 @@ paths: items: $ref: "#/components/schemas/JobSearchHit" + /srch/index/search/service_logs: + get: + summary: Search through service logs with a string query + operationId: searchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: mode + in: query + required: true + schema: + type: string + - name: worker_group + in: query + required: false + schema: + type: string + - name: hostname + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + "200": + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: a list of the terms that couldn't be parsed (and thus ignored) + type: array + items: + type: string + hits: + description: log files that matched the query + type: array + items: + $ref: "#/components/schemas/LogSearchHit" + + /srch/index/search/count_service_logs: + get: + summary: Search and count the log line hits on every provided host + operationId: countSearchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: hosts + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + "200": + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: a list of the terms that couldn't be parsed (and thus ignored) + type: array + items: + type: string + count_per_host: + description: count of log lines that matched the query per hostname + type: object components: securitySchemes: @@ -11357,6 +11543,17 @@ components: type: string route_path: type: string + static_asset_config: + type: object + properties: + s3: + type: string + storage: + type: string + filename: + type: string + required: + - s3 is_flow: type: boolean extra_perms: @@ -11403,6 +11600,17 @@ components: type: string route_path: type: string + static_asset_config: + type: object + properties: + s3: + type: string + storage: + type: string + filename: + type: string + required: + - s3 is_flow: type: boolean http_method: @@ -11436,6 +11644,17 @@ components: type: string route_path: type: string + static_asset_config: + type: object + properties: + s3: + type: string + storage: + type: string + filename: + type: string + required: + - s3 is_flow: type: boolean http_method: @@ -11522,6 +11741,12 @@ components: required: - key - value + initial_messages: + type: array + items: + $ref: "#/components/schemas/WebsocketTriggerInitialMessage" + url_runnable_args: + $ref: "#/components/schemas/ScriptArgs" required: - path @@ -11535,6 +11760,8 @@ components: - workspace_id - enabled - filters + - initial_messages + - url_runnable_args NewWebsocketTrigger: type: object @@ -11560,6 +11787,12 @@ components: required: - key - value + initial_messages: + type: array + items: + $ref: "#/components/schemas/WebsocketTriggerInitialMessage" + url_runnable_args: + $ref: "#/components/schemas/ScriptArgs" required: - path @@ -11567,6 +11800,8 @@ components: - url - is_flow - filters + - initial_messages + - url_runnable_args EditWebsocketTrigger: type: object @@ -11590,6 +11825,12 @@ components: required: - key - value + initial_messages: + type: array + items: + $ref: "#/components/schemas/WebsocketTriggerInitialMessage" + url_runnable_args: + $ref: "#/components/schemas/ScriptArgs" required: - path @@ -11597,6 +11838,34 @@ components: - url - is_flow - filters + - initial_messages + - url_runnable_args + + WebsocketTriggerInitialMessage: + anyOf: + - type: object + properties: + raw_message: + type: string + required: + - raw_message + - type: object + properties: + runnable_result: + type: object + properties: + path: + type: string + args: + $ref: "#/components/schemas/ScriptArgs" + is_flow: + type: boolean + required: + - path + - args + - is_flow + required: + - runnable_result Group: type: object @@ -11917,6 +12186,10 @@ components: type: object additionalProperties: type: object + s3_inputs: + type: array + items: + type: object execution_mode: type: string enum: [viewer, publisher, anonymous] @@ -12500,6 +12773,12 @@ components: dancer: type: string + LogSearchHit: + type: object + properties: + dancer: + type: string + AutoscalingEvent: type: object properties: @@ -12517,3 +12796,24 @@ components: applied_at: type: string format: date-time + + CriticalAlert: + type: object + properties: + id: + type: integer + description: Unique identifier for the alert + alert_type: + type: string + description: Type of alert (e.g., critical_error) + message: + type: string + description: The message content of the alert + created_at: + type: string + format: date-time + description: Time when the alert was created + acknowledged: + type: boolean + nullable: true + description: Acknowledgment status of the alert, can be true, false, or null if not set diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index cd65136a2a..4ec07393dc 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -7,6 +7,12 @@ use std::collections::HashMap; * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. */ + +#[cfg(feature = "parquet")] +use crate::{job_helpers_ee::{ + get_random_file_name, get_s3_resource, get_workspace_s3_resource, upload_file_internal, + UploadFileResponse, +}, users::fetch_api_authed_from_permissioned_as}; use crate::{ db::{ApiAuthed, DB}, resources::get_resource_value_interpolated_internal, @@ -23,7 +29,13 @@ use axum::{ Router, }; use hyper::StatusCode; +#[cfg(feature = "parquet")] +use itertools::Itertools; use magic_crypt::MagicCryptTrait; +#[cfg(feature = "parquet")] +use object_store::{Attribute, Attributes}; +#[cfg(feature = "parquet")] +use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue}; use sha2::{Digest, Sha256}; @@ -32,6 +44,8 @@ use sqlx::{types::Uuid, FromRow}; use std::str; use windmill_audit::audit_ee::audit_log; use windmill_audit::ActionKind; +#[cfg(feature = "parquet")] +use windmill_common::s3_helpers::build_object_store_client; use windmill_common::{ apps::ListAppQuery, db::UserDB, @@ -69,6 +83,7 @@ pub fn workspaced_service() -> Router { pub fn unauthed_service() -> Router { Router::new() .route("/execute_component/*path", post(execute_component)) + .route("/upload_s3_file/*path", post(upload_s3_file_from_app)) .route("/public_app/:secret", get(get_public_app_by_secret)) .route("/public_resource/*path", get(get_public_resource)) } @@ -179,6 +194,14 @@ pub struct PolicyTriggerableInputs { allow_user_resources: AllowUserResources, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct S3Input { + allowed_resources: Vec, + allow_user_resources: bool, + allow_workspace_resource: bool, + file_key_regex: String, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Policy { pub on_behalf_of: Option, @@ -192,6 +215,7 @@ pub struct Policy { #[serde(skip_serializing_if = "Option::is_none")] pub triggerables_v2: Option>, pub execution_mode: ExecutionMode, + pub s3_inputs: Option>, } #[derive(Deserialize)] @@ -432,9 +456,7 @@ async fn get_latest_version( authed: ApiAuthed, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - ) -> JsonResult> { - let mut tx = user_db.begin(&authed).await?; let row = sqlx::query!( "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg @@ -457,7 +479,6 @@ async fn get_latest_version( } else { return Ok(Json(None)); } - } async fn update_app_history( @@ -1067,6 +1088,49 @@ fn digest(code: &str) -> String { format!("rawscript/{:x}", result) } +async fn get_on_behalf_details_from_policy_and_authed( + policy: &Policy, + opt_authed: &Option, +) -> Result<(String, String, String)> { + let (username, permissioned_as, email) = match policy.execution_mode { + ExecutionMode::Anonymous => { + let username = opt_authed + .as_ref() + .map(|a| a.username.clone()) + .unwrap_or_else(|| "anonymous".to_string()); + let (permissioned_as, email) = get_on_behalf_of(&policy)?; + (username, permissioned_as, email) + } + ExecutionMode::Publisher => { + let username = opt_authed + .as_ref() + .map(|a| a.username.clone()) + .ok_or_else(|| { + Error::BadRequest( + "publisher execution mode requires authentication".to_string(), + ) + })?; + let (permissioned_as, email) = get_on_behalf_of(&policy)?; + (username, permissioned_as, email) + } + ExecutionMode::Viewer => { + let (username, email) = opt_authed + .as_ref() + .map(|a| (a.username.clone(), a.email.clone())) + .ok_or_else(|| { + Error::BadRequest("Required to be authed in viewer mode".to_string()) + })?; + ( + username.clone(), + username_to_permissioned_as(&username), + email, + ) + } + }; + + Ok((username, permissioned_as, email)) +} + async fn execute_component( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, @@ -1129,6 +1193,7 @@ async fn execute_component( triggerables_v2: Some(hm), on_behalf_of: None, on_behalf_of_email: None, + s3_inputs: None, } } _ => { @@ -1146,41 +1211,8 @@ async fn execute_component( } }; - let (username, permissioned_as, email) = match policy.execution_mode { - ExecutionMode::Anonymous => { - let username = opt_authed - .as_ref() - .map(|a| a.username.clone()) - .unwrap_or_else(|| "anonymous".to_string()); - let (permissioned_as, email) = get_on_behalf_of(&policy)?; - (username, permissioned_as, email) - } - ExecutionMode::Publisher => { - let username = opt_authed - .as_ref() - .map(|a| a.username.clone()) - .ok_or_else(|| { - Error::BadRequest( - "publisher execution mode requires authentication".to_string(), - ) - })?; - let (permissioned_as, email) = get_on_behalf_of(&policy)?; - (username, permissioned_as, email) - } - ExecutionMode::Viewer => { - let (username, email) = opt_authed - .as_ref() - .map(|a| (a.username.clone(), a.email.clone())) - .ok_or_else(|| { - Error::BadRequest("Required to be authed in viewer mode".to_string()) - })?; - ( - username.clone(), - username_to_permissioned_as(&username), - email, - ) - } - }; + let (username, permissioned_as, email) = + get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?; let (job_payload, (args, job_id), tag) = match payload { ExecuteApp { args, component, raw_code: Some(raw_code), path: None, .. } => { @@ -1249,6 +1281,248 @@ async fn execute_component( Ok(uuid.to_string()) } +#[cfg(not(feature = "parquet"))] +async fn upload_s3_file_from_app() -> Result<()> { + return Err(Error::BadRequest( + "This endpoint requires the parquet feature to be enabled".to_string(), + )); +} + + +#[cfg(feature = "parquet")] +#[derive(Debug, Deserialize, Clone)] +struct UploadFileToS3Query { + file_key: Option, + file_extension: Option, + s3_resource_path: Option, + content_type: Option, + content_disposition: Option, + force_viewer_file_key_regex: Option, + force_viewer_allow_user_resources: Option, + force_viewer_allow_workspace_resource: Option, + force_viewer_allowed_resources: Option, +} + +#[cfg(feature = "parquet")] +async fn upload_s3_file_from_app( + OptAuthed(opt_authed): OptAuthed, + Extension(db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Query(query): Query, + request: axum::extract::Request, +) -> JsonResult { + let policy = if let Some(file_key_regex) = query.force_viewer_file_key_regex { + Some(Policy { + execution_mode: ExecutionMode::Viewer, + triggerables: None, + triggerables_v2: None, + on_behalf_of: None, + on_behalf_of_email: None, + s3_inputs: Some(vec![S3Input { + file_key_regex: file_key_regex, + allow_user_resources: query.force_viewer_allow_user_resources.unwrap_or(false), + allow_workspace_resource: query + .force_viewer_allow_workspace_resource + .unwrap_or(false), + allowed_resources: query + .force_viewer_allowed_resources + .map(|s| s.split(',').map(|s| s.to_string()).collect()) + .unwrap_or_default(), + }]), + }) + } else { + let policy_o = sqlx::query_scalar!( + "SELECT policy from app WHERE path = $1 AND workspace_id = $2", + &path.0, + &w_id + ) + .fetch_optional(&db) + .await?; + + policy_o + .map(|p| serde_json::from_value::(p).map_err(to_anyhow)) + .transpose()? + }; + + let user_db = UserDB::new(db.clone()); + + let (s3_resource_opt, file_key) = if policy.as_ref().is_some_and(|p| p.s3_inputs.is_some()) { + let policy = policy.unwrap(); + let s3_inputs = policy.s3_inputs.as_ref().unwrap(); + + let (username, permissioned_as, email) = + get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?; + + let on_behalf_authed = + fetch_api_authed_from_permissioned_as(permissioned_as, email, &w_id, &db, username) + .await?; + + if let Some(file_key) = query.file_key { + // file key is provided => requires workspace, user or list policy and must match the regex + let matching_s3_inputs = if let Some(ref s3_resource_path) = query.s3_resource_path { + s3_inputs + .iter() + .filter(|s3_input| { + s3_input.allowed_resources.contains(s3_resource_path) + || s3_input.allow_user_resources + }) + .sorted_by_key(|i| i.allow_user_resources) // consider user resources last + .collect::>() + } else { + s3_inputs + .iter() + .filter(|s3_input| s3_input.allow_workspace_resource) + .collect::>() + }; + + let matched_input = matching_s3_inputs.iter().find(|s3_input| { + match Regex::new(&s3_input.file_key_regex) { + Ok(re) => re.is_match(&file_key), + Err(e) => { + tracing::error!("Error compiling regex: {}", e); + false + } + } + }); + + if let Some(matched_input) = matched_input { + if let Some(ref s3_resource_path) = query.s3_resource_path { + if matched_input.allow_user_resources { + if let Some(authed) = opt_authed { + ( + Some( + get_s3_resource( + &authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } else { + return Err(Error::BadRequest( + "User resources are not allowed without being logged in" + .to_string(), + )); + } + } else { + ( + Some( + get_s3_resource( + &on_behalf_authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } + } else { + let (_, s3_resource_opt) = + get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None) + .await?; + (s3_resource_opt, file_key) + } + } else { + return Err(Error::BadRequest( + "No matching s3 resource found for the given file key".to_string(), + )); + } + } else { + // no file key => requires unnamed upload policy => allow workspace resource and file_key_regex is empty + let has_unnamed_policy = s3_inputs.iter().any(|s3_input| { + s3_input.allow_workspace_resource && s3_input.file_key_regex.is_empty() + }); + + if !has_unnamed_policy { + return Err(Error::BadRequest( + "no policy found for unnamed s3 file uplooad".to_string(), + )); + } + + // for now, we place all files into `windmill_uploads` folder with a random name + // TODO: make the folder configurable via the workspace settings + let file_key = get_random_file_name(query.file_extension); + + let (_, s3_resource_opt) = + get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None).await?; + + (s3_resource_opt, file_key) + } + } else { + // backward compatibility (no policy) + // if no policy but logged in, use the user's auth to get the s3 resource + if let Some(authed) = opt_authed { + let file_key = query + .file_key + .unwrap_or_else(|| get_random_file_name(query.file_extension)); + + if let Some(ref s3_resource_path) = query.s3_resource_path { + ( + Some( + get_s3_resource( + &authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } else { + let (_, s3_resource) = + get_workspace_s3_resource(&authed, &db, None, "", &w_id, None).await?; + + (s3_resource, file_key) + } + } else { + return Err(Error::BadRequest("Missing s3 policy".to_string())); + } + }; + + let s3_resource = s3_resource_opt.ok_or(Error::InternalErr( + "No files storage resource defined at the workspace level".to_string(), + ))?; + let s3_client = build_object_store_client(&s3_resource).await?; + + let options = Attributes::from_iter(vec![ + ( + Attribute::ContentType, + query.content_type.unwrap_or_else(|| { + mime_guess::from_path(&file_key) + .first_or_octet_stream() + .to_string() + }), + ), + ( + Attribute::ContentDisposition, + query.content_disposition.unwrap_or("inline".to_string()), + ), + ]) + .into(); + + upload_file_internal(s3_client, &file_key, request, options).await?; + + return Ok(Json(UploadFileResponse { file_key })); +} + fn get_on_behalf_of(policy: &Policy) -> Result<(String, String)> { let permissioned_as = policy .on_behalf_of diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 4fc9ce514f..f02f91dc41 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -1209,6 +1209,7 @@ mod tests { .into(), hash: None, tag_override: None, + is_trigger: None, }), stop_after_if: None, stop_after_all_iters_if: None, @@ -1236,6 +1237,7 @@ mod tests { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }), stop_after_if: Some(StopAfterIf { expr: "foo = 'bar'".to_string(), @@ -1290,6 +1292,7 @@ mod tests { input_transforms: HashMap::new(), hash: None, tag_override: None, + is_trigger: None, } .into(), stop_after_if: Some(StopAfterIf { diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 61757cb164..2c323c0e8c 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -1,24 +1,5 @@ -use axum::{ - extract::{Path, Query}, - response::IntoResponse, - routing::{delete, get, post}, - Extension, Json, Router, -}; -use http::{HeaderMap, StatusCode}; -use serde::{Deserialize, Serialize}; -use sql_builder::{bind::Bind, SqlBuilder}; -use sqlx::prelude::FromRow; -use std::collections::HashMap; -use tower_http::cors::CorsLayer; -use windmill_audit::{audit_ee::audit_log, ActionKind}; -use windmill_common::{ - db::UserDB, - error::{self, JsonResult}, - utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, - worker::{to_raw_value, CLOUD_HOSTED}, -}; -use windmill_queue::PushArgsOwned; - +#[cfg(feature = "parquet")] +use crate::job_helpers_ee::get_workspace_s3_resource; use crate::{ db::{ApiAuthed, DB}, jobs::{ @@ -27,6 +8,31 @@ use crate::{ }, users::{fetch_api_authed, OptAuthed}, }; +use axum::{ + extract::{Path, Query}, + response::IntoResponse, + routing::{delete, get, post}, + Extension, Json, Router, +}; +#[cfg(feature = "parquet")] +use http::header::IF_NONE_MATCH; +use http::{HeaderMap, StatusCode}; +use serde::{Deserialize, Serialize}; +use sql_builder::{bind::Bind, SqlBuilder}; +use sqlx::prelude::FromRow; +use std::collections::HashMap; +use tower_http::cors::CorsLayer; +use windmill_audit::{audit_ee::audit_log, ActionKind}; +#[cfg(feature = "parquet")] +use windmill_common::s3_helpers::build_object_store_client; +use windmill_common::{ + db::UserDB, + error::{self, JsonResult}, + s3_helpers::S3Object, + utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, + worker::{to_raw_value, CLOUD_HOSTED}, +}; +use windmill_queue::PushArgsOwned; lazy_static::lazy_static! { static ref ROUTE_PATH_KEY_RE: regex::Regex = regex::Regex::new(r"/:\w+").unwrap(); @@ -99,6 +105,7 @@ struct NewTrigger { is_async: bool, requires_auth: bool, http_method: HttpMethod, + static_asset_config: Option>, } #[derive(FromRow, Serialize)] @@ -116,6 +123,7 @@ struct Trigger { is_async: bool, requires_auth: bool, http_method: HttpMethod, + static_asset_config: Option>, } #[derive(Deserialize)] @@ -127,6 +135,7 @@ struct EditTrigger { is_async: bool, requires_auth: bool, http_method: HttpMethod, + static_asset_config: Option>, } #[derive(Deserialize)] @@ -182,7 +191,7 @@ async fn get_trigger( let path = path.to_path(); let trigger = sqlx::query_as!( Trigger, - r#"SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as "http_method: _", edited_by, email, edited_at, extra_perms, is_async, requires_auth + r#"SELECT workspace_id, path, route_path, route_path_key, script_path, is_flow, http_method as "http_method: _", edited_by, email, edited_at, extra_perms, is_async, requires_auth, static_asset_config as "static_asset_config: _" FROM http_trigger WHERE workspace_id = $1 AND path = $2"#, w_id, @@ -209,7 +218,7 @@ async fn create_trigger( let mut tx = user_db.begin(&authed).await?; sqlx::query!( - "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now())", + "INSERT INTO http_trigger (workspace_id, path, route_path, route_path_key, script_path, is_flow, is_async, requires_auth, http_method, static_asset_config, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now())", w_id, ct.path, ct.route_path, @@ -218,7 +227,8 @@ async fn create_trigger( ct.is_flow, ct.is_async, ct.requires_auth, - ct.http_method as HttpMethod, + ct.http_method as _, + ct.static_asset_config as _, &authed.username, &authed.email ) @@ -261,14 +271,15 @@ async fn update_trigger( sqlx::query!( "UPDATE http_trigger - SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, edited_by = $7, email = $8, is_async = $9, requires_auth = $10, edited_at = now() - WHERE workspace_id = $11 AND path = $12", + SET route_path = $1, route_path_key = $2, script_path = $3, path = $4, is_flow = $5, http_method = $6, static_asset_config = $7, edited_by = $8, email = $9, is_async = $10, requires_auth = $11, edited_at = now() + WHERE workspace_id = $12 AND path = $13", ct.route_path, &route_path_key, ct.script_path, ct.path, ct.is_flow, - ct.http_method as HttpMethod, + ct.http_method as _, + ct.static_asset_config as _, &authed.username, &authed.email, ct.is_async, @@ -279,12 +290,13 @@ async fn update_trigger( .execute(&mut *tx).await?; } else { sqlx::query!( - "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, edited_by = $5, email = $6, is_async = $7, requires_auth = $8, edited_at = now() - WHERE workspace_id = $9 AND path = $10", + "UPDATE http_trigger SET script_path = $1, path = $2, is_flow = $3, http_method = $4, static_asset_config = $5, edited_by = $6, email = $7, is_async = $8, requires_auth = $9, edited_at = now() + WHERE workspace_id = $10 AND path = $11", ct.script_path, ct.path, ct.is_flow, - ct.http_method as HttpMethod, + ct.http_method as _, + ct.static_asset_config as _, &authed.username, &authed.email, ct.is_async, @@ -405,6 +417,7 @@ struct TriggerRoute { edited_by: String, email: String, http_method: HttpMethod, + static_asset_config: Option>, } async fn get_http_route_trigger( @@ -421,7 +434,7 @@ async fn get_http_route_trigger( let route_path = StripPath(splitted.collect::>().join("/")); let triggers = sqlx::query_as!( TriggerRoute, - r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _" FROM http_trigger WHERE workspace_id = $1"#, + r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _", static_asset_config as "static_asset_config: _" FROM http_trigger WHERE workspace_id = $1"#, w_id ) .fetch_all(db) @@ -430,7 +443,7 @@ async fn get_http_route_trigger( } else { let triggers = sqlx::query_as!( TriggerRoute, - r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _" FROM http_trigger"#, + r#"SELECT path, script_path, is_flow, route_path, workspace_id, is_async, requires_auth, edited_by, email, http_method as "http_method: _", static_asset_config as "static_asset_config: _" FROM http_trigger"#, ) .fetch_all(db) .await?; @@ -518,6 +531,90 @@ async fn route_job( Ok(trigger) => trigger, Err(e) => return e.into_response(), }; + + #[cfg(not(feature = "parquet"))] + if trigger.static_asset_config.is_some() { + return error::Error::InternalErr( + "Static asset configuration is not supported in this build".to_string(), + ) + .into_response(); + } + + #[cfg(feature = "parquet")] + if let Some(sqlx::types::Json(config)) = trigger.static_asset_config { + let build_static_response_f = async { + let (_, s3_resource_opt) = get_workspace_s3_resource( + &authed, + &db, + None, + &"NO_TOKEN".to_string(), // no token is provided in this case + &trigger.workspace_id, + config.storage, + ) + .await?; + let s3_resource = s3_resource_opt.ok_or(error::Error::InternalErr( + "No files storage resource defined at the workspace level".to_string(), + ))?; + let s3_client = build_object_store_client(&s3_resource).await?; + let path = object_store::path::Path::from(config.s3); + let s3_object = s3_client.get(&path).await.map_err(|err| { + tracing::warn!("Error retrieving file from S3: {:?}", err); + error::Error::InternalErr(format!("Error retrieving file: {}", err.to_string())) + })?; + let mut response_headers = http::HeaderMap::new(); + if let Some(ref e_tag) = s3_object.meta.e_tag { + if let Some(if_none_match) = headers.get(IF_NONE_MATCH) { + if if_none_match == e_tag { + return Ok::<_, error::Error>(( + StatusCode::NOT_MODIFIED, + response_headers, + axum::body::Body::empty(), + )); + } + } + if let Ok(e_tag) = e_tag.parse() { + response_headers.insert("etag", e_tag); + } + } + response_headers.insert( + "content-type", + s3_object + .attributes + .get(&object_store::Attribute::ContentType) + .map(|s| s.parse().ok()) + .flatten() + .unwrap_or("application/octet-stream".parse().unwrap()), + ); + response_headers.insert( + "content-disposition", + config.filename.as_ref().map_or_else( + || { + s3_object + .attributes + .get(&object_store::Attribute::ContentDisposition) + .map(|s| s.parse().ok()) + .flatten() + .unwrap_or("inline".parse().unwrap()) + }, + |filename| { + format!("inline; filename=\"{}\"", filename) + .parse() + .unwrap_or("inline".parse().unwrap()) + }, + ), + ); + + let body_stream = axum::body::Body::from_stream(s3_object.into_stream()); + Ok::<_, error::Error>((StatusCode::OK, response_headers, body_stream)) + }; + match build_static_response_f.await { + Ok((status, headers, body_stream)) => { + return (status, headers, body_stream).into_response() + } + Err(e) => return e.into_response(), + } + } + let headers = headers .iter() .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs index 61a946bb84..2ccca92c27 100644 --- a/backend/windmill-api/src/indexer_ee.rs +++ b/backend/windmill-api/src/indexer_ee.rs @@ -3,3 +3,7 @@ use axum::Router; pub fn workspaced_service() -> Router { Router::new() } + +pub fn global_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs index 61a946bb84..3033a2ec5a 100644 --- a/backend/windmill-api/src/job_helpers_ee.rs +++ b/backend/windmill-api/src/job_helpers_ee.rs @@ -1,5 +1,61 @@ use axum::Router; +use serde::Serialize; +use uuid::Uuid; +use windmill_common::s3_helpers::StorageResourceType; + +#[cfg(feature = "parquet")] +use crate::db::{ApiAuthed, DB}; +#[cfg(feature = "parquet")] +use object_store::{ObjectStore, PutMultipartOpts}; +#[cfg(feature = "parquet")] +use std::sync::Arc; +use windmill_common::error; +#[cfg(feature = "parquet")] +use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource}; +#[derive(Serialize)] +pub struct UploadFileResponse { + pub file_key: String, +} pub fn workspaced_service() -> Router { Router::new() } + +#[cfg(feature = "parquet")] +pub async fn get_workspace_s3_resource<'c>( + _authed: &ApiAuthed, + _db: &DB, + _user_db: Option, + _token: &str, + _w_id: &str, + _storage: Option, +) -> windmill_common::error::Result<(Option, Option)> { + // implementation is not open source + Ok((None, None)) +} + +pub fn get_random_file_name(_file_extension: Option) -> String { + todo!() +} + +pub async fn get_s3_resource<'c>( + _authed: &ApiAuthed, + _db: &DB, + _user_db: Option, + _token: &str, + _w_id: &str, + _resource_path: &str, + _resource_type: Option, + _job_id: Option, +) -> error::Result { + todo!() +} + +pub async fn upload_file_internal( + _s3_client: Arc, + _file_key: &str, + _request: axum::extract::Request, + _options: PutMultipartOpts, +) -> error::Result<()> { + todo!() +} diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 7244b2be17..433c859619 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -65,7 +65,7 @@ use windmill_common::{ db::UserDB, error::{self, to_anyhow, Error}, flow_status::{Approval, FlowStatus, FlowStatusModule}, - flows::FlowValue, + flows::{add_virtual_items_if_necessary, FlowValue}, jobs::{script_path_to_payload, CompletedJob, JobKind, JobPayload, QueuedJob, RawCode}, oauth2::HmacSha256, scripts::{ScriptHash, ScriptLang}, @@ -4275,18 +4275,26 @@ async fn run_flow_dependencies_job( wait_result } +#[derive(Deserialize)] +struct BatchRawScript { + content: String, + language: Option, + lock: Option, +} + #[derive(Deserialize)] struct BatchInfo { kind: String, flow_value: Option, path: Option, + rawscript: Option, } #[tracing::instrument(level = "trace", skip_all)] async fn add_batch_jobs( authed: ApiAuthed, Extension(db): Extension, - Extension(rsmq): Extension>, + Extension(_rsmq): Extension>, Path((w_id, n)): Path<(String, i32)>, Json(batch_info): Json, ) -> error::JsonResult> { @@ -4302,6 +4310,10 @@ async fn add_batch_jobs( concurrent_limit, concurrent_time_window_s, timeout, + raw_code, + raw_lock, + raw_flow, + flow_status ) = match batch_info.kind.as_str() { "script" => { if let Some(path) = batch_info.path { @@ -4329,6 +4341,10 @@ async fn add_batch_jobs( concurrent_limit, concurrency_time_window_s, timeout, + None, + None, + None, + None, ) } else { Err(anyhow::anyhow!( @@ -4336,61 +4352,72 @@ async fn add_batch_jobs( ))? } } - "flow" => { - let mut uuids: Vec = Vec::new(); - let payload = if let Some(ref fv) = batch_info.flow_value { - JobPayload::RawFlow { value: fv.clone(), path: None, restarted_from: None } - } else { - if let Some(path) = batch_info.path.as_ref() { - JobPayload::Flow { - path: path.to_string(), - dedicated_worker: None, - apply_preprocessor: false, - } - } else { - Err(anyhow::anyhow!( - "Path is required if no value is not provided" - ))? - } - }; - let mut tx = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq); - for _ in 0..n { - let ehm = HashMap::new(); - let (uuid, ntx) = push( - &db, - tx, - &w_id, - payload.clone(), - PushArgs::from(&ehm), - authed.display_username(), - &authed.email, - username_to_permissioned_as(&authed.username), + "rawscript" => { + if let Some(rawscript) = batch_info.rawscript { + ( + None, + None, + JobKind::Preview, + rawscript.language, None, None, None, None, None, - false, - false, - None, - true, + Some(rawscript.content), + rawscript.lock, None, None, - None, - None, - Some(&authed.clone().into()), ) - .await?; - tx = PushIsolationLevel::Transaction(ntx); - uuids.push(uuid); + } else { + Err(anyhow::anyhow!( + "rawscript is required for `rawscript` kind" + ))? } - match tx { - PushIsolationLevel::Transaction(tx) => { - tx.commit().await?; - } - _ => (), - } - return Ok(Json(uuids)); + } + "flow" => { + let (mut value, job_kind, path) = if let Some(value) = batch_info.flow_value { + (value, JobKind::FlowPreview, None) + } else if let Some(path) = batch_info.path { + let value_json = sqlx::query!( + "SELECT flow_version.value AS \"value: sqlx::types::Json>\" FROM flow + LEFT JOIN flow_version + ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] + WHERE flow.path = $1 AND flow.workspace_id = $2", + &path, &w_id + ) + .fetch_optional(&db) + .await? + .ok_or_else(|| Error::InternalErr(format!("not found flow at path {:?}", path)))?; + let value = + serde_json::from_str::(value_json.value.get()).map_err(|err| { + Error::InternalErr(format!( + "could not convert json to flow for {path}: {err:?}" + )) + })?; + (value, JobKind::Flow, Some(path)) + } else { + Err(anyhow::anyhow!( + "Path is required if no value is not provided" + ))? + }; + add_virtual_items_if_necessary(&mut value.modules); + let flow_status = FlowStatus::new(&value); + ( + None, // script_hash + path, // script_path + job_kind, // job_kind + None, // language + None, // dedicated_worker + value.concurrency_key.clone(), // custom_concurrency_key + value.concurrent_limit.clone(), // concurrent_limit + value.concurrency_time_window_s, // concurrency_time_window_s + None, // timeout + None, // raw_code + None, // raw_lock + Some(value), // raw_flow + Some(flow_status), // flow_status + ) } "noop" => ( None, @@ -4402,6 +4429,10 @@ async fn add_batch_jobs( None, None, None, + None, + None, + None, + None, ), _ => { return Err(error::Error::BadRequest(format!( @@ -4428,8 +4459,8 @@ async fn add_batch_jobs( select gen_random_uuid() as uuid from generate_series(1, $11) ) INSERT INTO queue - (id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout) - (SELECT uuid, $1, $2, $3, $4, ('{ "uuid": "' || uuid || '" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14 FROM uuid_table) + (id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout, raw_code, raw_lock, raw_flow, flow_status) + (SELECT uuid, $1, $2, $3, $4, ('{ "uuid": "' || uuid || '" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14, $15, $16, $17, $18 FROM uuid_table) RETURNING id"#, hash.map(|h| h.0), path, @@ -4444,7 +4475,11 @@ async fn add_batch_jobs( n, concurrent_limit, concurrent_time_window_s, - timeout + timeout, + raw_code, + raw_lock, + raw_flow.map(sqlx::types::Json) as Option>, + flow_status.map(sqlx::types::Json) as Option> ) .fetch_all(&db) .await?; diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 3ea78ffd6d..5a7f7fcded 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -154,18 +154,18 @@ pub async fn add_webhook_allowed_origin( type IndexReader = (); #[cfg(not(feature = "tantivy"))] -type IndexWriter = (); +type ServiceLogIndexReader = (); #[cfg(feature = "tantivy")] -type IndexReader = windmill_indexer::indexer_ee::IndexReader; +type IndexReader = windmill_indexer::completed_runs_ee::IndexReader; #[cfg(feature = "tantivy")] -type IndexWriter = windmill_indexer::indexer_ee::IndexWriter; +type ServiceLogIndexReader = windmill_indexer::service_logs_ee::ServiceLogIndexReader; pub async fn run_server( db: DB, rsmq: Option, - index_reader: Option, - index_writer: Option, + job_index_reader: Option, + log_index_reader: Option, addr: SocketAddr, mut rx: tokio::sync::broadcast::Receiver<()>, port_tx: tokio::sync::oneshot::Sender, @@ -205,8 +205,9 @@ pub async fn run_server( .layer(Extension(rsmq.clone())) .layer(Extension(user_db.clone())) .layer(Extension(auth_cache.clone())) - .layer(Extension(index_reader)) - .layer(Extension(index_writer)) + .layer(Extension(job_index_reader)) + .layer(Extension(log_index_reader)) + // .layer(Extension(index_writer)) .layer(CookieManagerLayer::new()) .layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone()))) .layer(DefaultBodyLimit::max( @@ -322,6 +323,10 @@ pub async fn run_server( "/srch/w/:workspace_id/index", indexer_ee::workspaced_service(), ) + .nest( + "/srch/index", + indexer_ee::global_service(), + ) .nest("/oidc", oidc_ee::global_service()) .nest( "/saml", diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 0299145a38..5dc46e4382 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -58,7 +58,10 @@ pub fn global_service() -> Router { ) .route("/renew_license_key", post(renew_license_key)) .route("/customer_portal", post(create_customer_portal_session)) - .route("/test_critical_channels", post(test_critical_channels)); + .route("/test_critical_channels", post(test_critical_channels)) + .route("/critical_alerts", get(get_critical_alerts)) + .route("/critical_alerts/:id/acknowledge", post(acknowledge_critical_alert)) + .route("/critical_alerts/acknowledge_all", post(acknowledge_all_critical_alerts)); #[cfg(feature = "parquet")] { @@ -430,3 +433,117 @@ pub async fn test_critical_channels( pub async fn test_critical_channels() -> Result { Ok("Critical channels require EE".to_string()) } + +use serde::Serialize; + +#[derive(Serialize)] +pub struct CriticalAlert { + id: i32, + alert_type: String, + message: String, + created_at: chrono::DateTime, + acknowledged: Option, +} + +#[cfg(feature = "enterprise")] +#[derive(Deserialize)] +pub struct AlertQueryParams { + pub page: Option, + pub page_size: Option, + pub acknowledged: Option, +} + +#[cfg(feature = "enterprise")] +pub async fn get_critical_alerts( + Extension(db): Extension, + authed: ApiAuthed, + Query(params): Query, +) -> JsonResult> { + require_super_admin(&db, &authed.email).await?; + + // Default pagination values if not provided + 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; + + let alerts = if let Some(acknowledged) = params.acknowledged { + sqlx::query_as!( + CriticalAlert, + "SELECT id, alert_type, message, created_at, acknowledged + FROM alerts + WHERE acknowledged = $1 + ORDER BY created_at DESC + LIMIT $2 OFFSET $3", + acknowledged, + page_size, + offset + ) + .fetch_all(&db) + .await? + } else { + sqlx::query_as!( + CriticalAlert, + "SELECT id, alert_type, message, created_at, acknowledged + FROM alerts + ORDER BY created_at DESC + LIMIT $1 OFFSET $2", + page_size, + offset + ) + .fetch_all(&db) + .await? + }; + + Ok(Json(alerts)) +} + +#[cfg(not(feature = "enterprise"))] +pub async fn get_critical_alerts() -> error::Error { + error::Error::NotFound("Critical Alerts require EE".to_string()) +} + +#[cfg(feature = "enterprise")] +pub async fn acknowledge_critical_alert( + Extension(db): Extension, + authed: ApiAuthed, + Path(id): Path, +) -> error::Result { + require_super_admin(&db, &authed.email).await?; + + sqlx::query!( + "UPDATE alerts SET acknowledged = true WHERE id = $1", + id + ) + .execute(&db) + .await?; + + tracing::info!("Acknowledged critical alert with id: {}", id); + Ok("Critical alert acknowledged".to_string()) +} + +#[cfg(not(feature = "enterprise"))] +pub async fn acknowledge_critical_alert() -> error::Error { + error::Error::NotFound("Critical Alerts require EE".to_string()) +} + +#[cfg(feature = "enterprise")] +pub async fn acknowledge_all_critical_alerts( + Extension(db): Extension, + authed: ApiAuthed, +) -> error::Result { + require_super_admin(&db, &authed.email).await?; + + sqlx::query!( + "UPDATE alerts SET acknowledged = true WHERE acknowledged = false" + ) + .execute(&db) + .await?; + + tracing::info!("Acknowledged all unacknowledged critical alerts"); + Ok("All unacknowledged critical alerts acknowledged".to_string()) +} + +#[cfg(not(feature = "enterprise"))] +pub async fn acknowledge_all_critical_alerts() -> error::Error { + error::Error::NotFound("Critical Alerts require EE".to_string()) +} diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index 783732b1c0..0423727e68 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -23,7 +23,7 @@ pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFil } #[derive(RustEmbed)] -#[folder = "../../frontend/build/"] +#[folder = "${FRONTEND_BUILD_DIR:-../../frontend/build/}"] struct Asset; pub struct StaticFile(Uri); diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index c9a812c4a0..380084d524 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -129,7 +129,8 @@ fn username_override_from_label(label: Option) -> Option { Some(label) if label.starts_with("webhook-") || label.starts_with("http-") - || label.starts_with("email-") => + || label.starts_with("email-") + || label.starts_with("ws-") => { Some(label) } @@ -745,10 +746,20 @@ pub async fn fetch_api_authed( username_override: String, ) -> error::Result { let permissioned_as = username_to_permissioned_as(username.as_str()); + fetch_api_authed_from_permissioned_as(permissioned_as, email, w_id, db, username_override).await +} + +pub async fn fetch_api_authed_from_permissioned_as( + permissioned_as: String, + email: String, + w_id: &str, + db: &DB, + username_override: String, +) -> error::Result { let authed = fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?; Ok(ApiAuthed { - username: username, + username: authed.username, email: email, is_admin: authed.is_admin, is_operator: authed.is_operator, diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs index b94ed85461..e10cd94c78 100644 --- a/backend/windmill-api/src/websocket_triggers.rs +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -1,9 +1,10 @@ +use anyhow::Context; use axum::{ extract::{Path, Query}, routing::{delete, get, post}, Extension, Json, Router, }; -use futures::StreamExt; +use futures::{stream::SplitSink, SinkExt, StreamExt}; use http::StatusCode; use itertools::Itertools; use rand::seq::SliceRandom; @@ -11,16 +12,20 @@ use serde::{ de::{self, MapAccess, Visitor}, Deserialize, Deserializer, Serialize, }; -use serde_json::Value; +use serde_json::{value::RawValue, Value}; use sql_builder::{bind::Bind, SqlBuilder}; use sqlx::prelude::FromRow; use std::{collections::HashMap, fmt}; -use tokio_tungstenite::connect_async; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; +use uuid::Uuid; use windmill_audit::{audit_ee::audit_log, ActionKind}; use windmill_common::{ db::UserDB, - error::{self, JsonResult}, - utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, + error::{self, to_anyhow, JsonResult}, + utils::{ + not_found_if_none, paginate, report_critical_error, require_admin, Pagination, StripPath, + }, worker::{to_raw_value, CLOUD_HOSTED}, INSTANCE_NAME, }; @@ -28,9 +33,7 @@ use windmill_queue::PushArgsOwned; use crate::{ db::{ApiAuthed, DB}, - jobs::{ - run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, - }, + jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery}, users::fetch_api_authed, }; @@ -52,7 +55,29 @@ struct NewWebsocketTrigger { script_path: String, is_flow: bool, enabled: Option, - filters: Vec, + filters: Vec>, + initial_messages: Vec>, + url_runnable_args: Box, +} + +#[derive(Deserialize)] +struct JsonFilter { + key: String, + value: serde_json::Value, +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum Filter { + JsonFilter(JsonFilter), +} + +#[derive(Deserialize)] +enum InitialMessage { + #[serde(rename = "raw_message")] + RawMessage(String), + #[serde(rename = "runnable_result")] + RunnableResult { path: String, args: Box, is_flow: bool }, } #[derive(FromRow, Serialize, Clone)] @@ -70,7 +95,9 @@ pub struct WebsocketTrigger { extra_perms: serde_json::Value, error: Option, enabled: bool, - filters: Vec, + filters: Vec>>, + initial_messages: Vec>>, + url_runnable_args: sqlx::types::Json>, } #[derive(Deserialize)] @@ -79,7 +106,9 @@ struct EditWebsocketTrigger { url: String, script_path: String, is_flow: bool, - filters: Vec, + filters: Vec>, + initial_messages: Vec>, + url_runnable_args: Box, } #[derive(Deserialize)] @@ -133,14 +162,13 @@ async fn get_websocket_trigger( ) -> error::JsonResult { let mut tx = user_db.begin(&authed).await?; let path = path.to_path(); - let trigger = sqlx::query_as!( - WebsocketTrigger, + let trigger = sqlx::query_as::<_, WebsocketTrigger>( r#"SELECT * FROM websocket_trigger WHERE workspace_id = $1 AND path = $2"#, - w_id, - path, ) + .bind(w_id) + .bind(path) .fetch_optional(&mut *tx) .await?; tx.commit().await?; @@ -164,19 +192,27 @@ async fn create_websocket_trigger( require_admin(authed.is_admin, &authed.username)?; let mut tx = user_db.begin(&authed).await?; - sqlx::query_as!( - WebsocketTrigger, - "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now()) RETURNING *", - w_id, - ct.path, - ct.url, - ct.script_path, - ct.is_flow, - ct.enabled.unwrap_or(true), - &ct.filters, - &authed.username, - &authed.email + + let filters = ct.filters.into_iter().map(sqlx::types::Json).collect_vec(); + let initial_messages = ct + .initial_messages + .into_iter() + .map(sqlx::types::Json) + .collect_vec(); + sqlx::query_as::<_, WebsocketTrigger>( + "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, initial_messages, url_runnable_args, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now()) RETURNING *", ) + .bind(&w_id) + .bind(&ct.path) + .bind(ct.url) + .bind(ct.script_path) + .bind(ct.is_flow) + .bind(ct.enabled.unwrap_or(true)) + .bind(filters.as_slice()) + .bind(initial_messages.as_slice()) + .bind(sqlx::types::Json(ct.url_runnable_args)) + .bind(&authed.username) + .bind(&authed.email) .fetch_one(&mut *tx).await?; audit_log( @@ -204,15 +240,24 @@ async fn update_websocket_trigger( let path = path.to_path(); let mut tx = user_db.begin(&authed).await?; + let filters = ct.filters.into_iter().map(sqlx::types::Json).collect_vec(); + let initial_messages = ct + .initial_messages + .into_iter() + .map(sqlx::types::Json) + .collect_vec(); + // important to update server_id, last_server_ping and error to NULL to stop current websocket listener sqlx::query!( - "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, edited_by = $6, email = $7, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL - WHERE workspace_id = $8 AND path = $9", + "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL + WHERE workspace_id = $10 AND path = $11", ct.url, ct.script_path, ct.path, ct.is_flow, - &ct.filters, + filters.as_slice() as &[sqlx::types::Json>], + initial_messages.as_slice() as &[sqlx::types::Json>], + sqlx::types::Json(ct.url_runnable_args) as sqlx::types::Json>, &authed.username, &authed.email, w_id, @@ -335,8 +380,7 @@ async fn listen_to_unlistened_websockets( rsmq: &Option, killpill_rx: &tokio::sync::broadcast::Receiver<()>, ) -> () { - match sqlx::query_as!( - WebsocketTrigger, + match sqlx::query_as::<_, WebsocketTrigger>( r#"SELECT * FROM websocket_trigger WHERE enabled IS TRUE AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')"# @@ -422,8 +466,6 @@ impl<'de, 'a> Visitor<'de> for SupersetVisitor<'a> { if key == self.key { // Deserialize the value for the key and check if it's a superset let json_value: Value = map.next_value()?; - tracing::info!("json_value: {:?}", json_value); - tracing::info!("value_to_check: {:?}", self.value_to_check); return Ok(is_superset(&json_value, self.value_to_check)); } else { // Skip the value if it's not the one we're interested in @@ -470,149 +512,429 @@ where deserializer.deserialize_map(SupersetVisitor { key, value_to_check }) } +async fn wait_runnable_result( + path: String, + is_flow: bool, + args: &Box, + ws_trigger: &WebsocketTrigger, + username_override: String, + db: &DB, + rsmq: Option, +) -> error::Result { + let user_db = UserDB::new(db.clone()); + let authed = fetch_api_authed( + ws_trigger.edited_by.clone(), + ws_trigger.email.clone(), + &ws_trigger.workspace_id, + &db, + username_override, + ) + .await?; + + let args = serde_json::from_str::>>>(args.get()) + .map_err(|e| error::Error::BadRequest(format!("invalid json: {}", e)))? + .unwrap_or_else(HashMap::new); + + let label_prefix = Some(format!("ws-{}-", ws_trigger.path)); + let (_, job_id) = if is_flow { + run_flow_by_path_inner( + authed, + db.clone(), + user_db, + rsmq.clone(), + ws_trigger.workspace_id.clone(), + StripPath(path.clone()), + RunJobQuery::default(), + PushArgsOwned { args, extra: None }, + label_prefix, + ) + .await? + } else { + run_script_by_path_inner( + authed, + db.clone(), + user_db, + rsmq.clone(), + ws_trigger.workspace_id.clone(), + StripPath(path.clone()), + RunJobQuery::default(), + PushArgsOwned { args, extra: None }, + label_prefix, + ) + .await? + }; + + let start_time = tokio::time::Instant::now(); + + loop { + if start_time.elapsed() > tokio::time::Duration::from_secs(300) { + return Err(anyhow::anyhow!( + "Timed out after 5m waiting for runnable {path} (is_flow: {is_flow}) to complete", + ) + .into()); + } + + #[derive(sqlx::FromRow)] + struct RawResult { + result: Option>>, + success: bool, + } + + let result = sqlx::query_as::<_, RawResult>( + "SELECT result, success FROM completed_job WHERE id = $1 AND workspace_id = $2", + ) + .bind(Uuid::parse_str(&job_id).unwrap()) + .bind(&ws_trigger.workspace_id) + .fetch_optional(db) + .await; + + match result { + Ok(Some(r)) => { + if !r.success { + return Err(anyhow::anyhow!( + "Runnable {path} (is_flow: {is_flow}) failed: {:?}", + r.result + ) + .into()); + } else { + return Ok(r.result.map(|r| r.get().to_owned()).unwrap_or_default()); + } + } + Ok(None) => { + // not yet done, wait for 5s and check again + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + } + Err(err) => { + return Err(anyhow::anyhow!( + "Error fetching job result for runnable {path} (is_flow: {is_flow}): {err}", + ) + .into()); + } + } + } +} + +async fn send_initial_messages( + ws_trigger: &WebsocketTrigger, + mut writer: SplitSink>, Message>, + db: &DB, + rsmq: Option, +) -> error::Result<()> { + let initial_messages: Vec = ws_trigger + .initial_messages + .iter() + .filter_map(|m| serde_json::from_str(m.get()).ok()) + .collect_vec(); + + for start_message in initial_messages { + match start_message { + InitialMessage::RawMessage(msg) => { + let msg = if msg.starts_with("\"") && msg.ends_with("\"") { + msg[1..msg.len() - 1].to_string() + } else { + msg + }; + tracing::info!( + "Sending raw message initial message to websocket {}: {}", + ws_trigger.url, + msg + ); + writer + .send(tokio_tungstenite::tungstenite::Message::Text(msg)) + .await + .map_err(to_anyhow) + .with_context(|| "failed to send raw message")?; + } + InitialMessage::RunnableResult { path, is_flow, args } => { + tracing::info!( + "Running runnable {path} (is_flow: {is_flow}) for initial message to websocket {}", + ws_trigger.url, + ); + + let result = wait_runnable_result( + path.clone(), + is_flow, + &args, + ws_trigger, + "init".to_string(), + db, + rsmq.clone(), + ) + .await?; + + tracing::info!( + "Sending runnable {path} (is_flow: {is_flow}) result to websocket {}", + ws_trigger.url + ); + + let result = if result.starts_with("\"") && result.ends_with("\"") { + result[1..result.len() - 1].to_string() + } else { + result + }; + + writer + .send(tokio_tungstenite::tungstenite::Message::Text(result)) + .await + .map_err(to_anyhow) + .with_context(|| { + format!("Failed to send runnable {path} (is_flow: {is_flow}) result") + })?; + } + } + } + + Ok(()) +} + +async fn get_url_from_runnable( + path: &str, + is_flow: bool, + ws_trigger: &WebsocketTrigger, + db: &DB, + rsmq: Option, +) -> error::Result { + tracing::info!("Running runnable {path} (is_flow: {is_flow}) to get websocket URL",); + + let result = wait_runnable_result( + path.to_string(), + is_flow, + &ws_trigger.url_runnable_args.0, + ws_trigger, + "url".to_string(), + db, + rsmq, + ) + .await?; + + if result.starts_with("\"") && result.ends_with("\"") { + Ok(result[1..result.len() - 1].to_string()) + } else { + Err(anyhow::anyhow!("Runnable {path} (is_flow: {is_flow}) did not return a string").into()) + } +} + +async fn update_ping(db: &DB, ws_trigger: &WebsocketTrigger, error: Option<&str>) -> Option<()> { + match sqlx::query_scalar!( + "UPDATE websocket_trigger SET last_server_ping = now(), error = $1 WHERE workspace_id = $2 AND path = $3 AND server_id = $4 AND enabled IS TRUE RETURNING 1", + error, + ws_trigger.workspace_id, + ws_trigger.path, + *INSTANCE_NAME + ).fetch_optional(db).await { + Ok(updated) => { + if updated.flatten().is_none() { + tracing::info!("Websocket {} changed, disabled, or deleted, stopping...", ws_trigger.url); + return None; + } + }, + Err(err) => { + tracing::warn!("Error updating ping of websocket {}: {:?}", ws_trigger.url, err); + } + }; + + Some(()) +} + +async fn loop_ping(db: &DB, ws_trigger: &WebsocketTrigger, error: Option<&str>) -> () { + loop { + if let None = update_ping(db, ws_trigger, error).await { + return; + } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + } +} + +async fn disable_with_error(db: &DB, ws_trigger: &WebsocketTrigger, error: String) { + match sqlx::query!( + "UPDATE websocket_trigger SET enabled = FALSE, error = $1, server_id = NULL, last_server_ping = NULL WHERE workspace_id = $2 AND path = $3", + error, + ws_trigger.workspace_id, + ws_trigger.path, + ) + .execute(db).await { + Ok(_) => { + report_critical_error(format!("Disabling websocket {} because of error: {}", ws_trigger.url, error), db.clone()).await; + }, + Err(disable_err) => { + report_critical_error( + format!("Could not disable websocket {} with err {}, disabling because of error {}", ws_trigger.path, disable_err, error), + db.clone() + ).await; + } + } +} + async fn listen_to_websocket( ws_trigger: WebsocketTrigger, db: DB, rsmq: Option, mut killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> () { - async fn update_ping(db: DB, ws_trigger: &WebsocketTrigger, error: Option<&str>) -> Option<()> { - match sqlx::query_scalar!( - "UPDATE websocket_trigger SET last_server_ping = now(), error = $1 WHERE workspace_id = $2 AND path = $3 AND server_id = $4 AND enabled IS TRUE RETURNING 1", - error, - ws_trigger.workspace_id, - ws_trigger.path, - *INSTANCE_NAME - ).fetch_optional(&db).await { - Ok(updated) => { - if updated.flatten().is_none() { - tracing::info!("Websocket {} changed, disabled, or deleted, stopping...", ws_trigger.url); - return None; - } - }, - Err(err) => { - tracing::warn!("Error updating ping of websocket {}: {:?}", ws_trigger.url, err); - } - }; - - Some(()) - } + update_ping(&db, &ws_trigger, Some("Connecting...")).await; let url = ws_trigger.url.as_str(); - #[derive(Deserialize)] - struct JsonFilter { - key: String, - value: serde_json::Value, - } - - #[derive(Deserialize)] - #[serde(untagged)] - enum Filter { - JsonFilter(JsonFilter), - } let filters: Vec = ws_trigger .filters .iter() - .filter_map(|m| serde_json::from_value(m.clone()).ok()) + .filter_map(|m| serde_json::from_str(m.get()).ok()) .collect_vec(); loop { + let connect_url = if url.starts_with("$") { + if url.starts_with("$flow:") || url.starts_with("$script:") { + let path = url.splitn(2, ':').nth(1).unwrap(); + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + }, + _ = loop_ping(&db, &ws_trigger, Some( + "Waiting on runnable to return websocket URL..." + )) => { + return; + }, + url_result = get_url_from_runnable(path, url.starts_with("$flow:"), &ws_trigger, &db, rsmq.clone()) => match url_result { + Ok(url) => url, + Err(err) => { + disable_with_error( + &db, + &ws_trigger, + format!( + "Error getting websocket URL from runnable after 5 tries: {:?}", + err + ), + ) + .await; + return; + } + }, + } + } else { + disable_with_error( + &db, + &ws_trigger, + format!("Invalid websocket runnable path: {}", url), + ) + .await; + return; + } + } else { + url.to_string() + }; + tokio::select! { biased; _ = killpill_rx.recv() => { return; }, - connection = connect_async(url) => { + _ = loop_ping(&db, &ws_trigger, Some("Connecting...")) => { + return; + }, + connection = connect_async(connect_url) => { match connection { Ok((ws_stream, _)) => { tracing::info!("Listening to websocket {}", url); - if let None = update_ping(db.clone(), &ws_trigger, None).await { + if let None = update_ping(&db, &ws_trigger, None).await { return; } + let (writer, mut reader) = ws_stream.split(); let mut last_ping = tokio::time::Instant::now(); - let (_, mut read) = ws_stream.split(); - loop { - tokio::select! { - biased; - _ = killpill_rx.recv() => { - return; + + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + } + _ = async { + if let Err(err) = send_initial_messages(&ws_trigger, writer, &db, rsmq.clone()).await { + disable_with_error(&db, &ws_trigger, format!("Error sending initial messages: {:?}", err)).await; + } else { + // if initial messages sent successfully, wait forever + futures::future::pending::<()>().await; } - msg = read.next() => { - if let Some(msg) = msg { - if last_ping.elapsed() > tokio::time::Duration::from_secs(5) { - if let None = update_ping(db.clone(), &ws_trigger, None).await { + } => { + // was disabled => exit + return; + }, + _ = async { + loop { + tokio::select! { + biased; + msg = reader.next() => { + if let Some(msg) = msg { + if last_ping.elapsed() > tokio::time::Duration::from_secs(5) { + if let None = update_ping(&db, &ws_trigger, None).await { + return; + } + last_ping = tokio::time::Instant::now(); + } + match msg { + Ok(msg) => { + match msg { + tokio_tungstenite::tungstenite::Message::Text(text) => { + let mut should_handle = true; + for filter in &filters { + match filter { + Filter::JsonFilter(JsonFilter { key, value }) => { + let mut deserializer = serde_json::Deserializer::from_str(text.as_str()); + should_handle = match is_value_superset(&mut deserializer, key, &value) { + Ok(filter_match) => { + filter_match + }, + Err(err) => { + tracing::warn!("Error deserializing filter for websocket {}: {:?}", url, err); + false + } + }; + } + } + if !should_handle { + break; + } + } + if should_handle { + if let Err(err) = run_job(&db, rsmq.clone(), &ws_trigger, text).await { + report_critical_error(format!("Failed to trigger job from websocket {}: {:?}", ws_trigger.url, err), db.clone()).await; + }; + } + }, + _ => {} + } + }, + Err(err) => { + tracing::error!("Error reading from websocket {}: {:?}", url, err); + } + } + } else { + tracing::error!("Websocket {} closed", url); + if let None = + update_ping(&db, &ws_trigger, Some("Websocket closed")).await + { + return; + } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + break; + } + }, + _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => { + if let None = update_ping(&db, &ws_trigger, None).await { return; } last_ping = tokio::time::Instant::now(); - } - match msg { - Ok(msg) => { - match msg { - tokio_tungstenite::tungstenite::Message::Text(text) => { - let mut should_handle = true; - for filter in &filters { - match filter { - Filter::JsonFilter(JsonFilter { key, value }) => { - let mut deserializer = serde_json::Deserializer::from_str(text.as_str()); - should_handle = match is_value_superset(&mut deserializer, key, &value) { - Ok(filter_match) => { - filter_match - }, - Err(err) => { - tracing::warn!("Error deserializing filter for websocket {}: {:?}", url, err); - false - } - }; - } - } - if !should_handle { - break; - } - } - if should_handle { - let db_ = db.clone(); - let rsmq_ = rsmq.clone(); - let ws_trigger_ = ws_trigger.clone(); - tokio::spawn(async move { - let url = ws_trigger_.url.clone(); - if let Err(err) = run_job(db_, rsmq_, ws_trigger_, text).await { - tracing::error!("Error running job on websocket {}: {:?}", url, err); - }; - }); - } - }, - _ => {} - } - }, - Err(err) => { - tracing::error!("Error reading from websocket {}: {:?}", url, err); - } - } - } else { - tracing::error!("Websocket {} closed", url); - if let None = - update_ping(db.clone(), &ws_trigger, Some("Websocket closed")).await - { - return; - } - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - break; + }, } - }, - _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => { - if let None = update_ping(db.clone(), &ws_trigger, None).await { - return; - } - last_ping = tokio::time::Instant::now(); - }, + } + } => { + return; } - } + }; } Err(err) => { tracing::error!("Error connecting to websocket {}: {:?}", url, err); if let None = - update_ping(db.clone(), &ws_trigger, Some(err.to_string().as_str())).await + update_ping(&db, &ws_trigger, Some(err.to_string().as_str())).await { return; } @@ -625,16 +947,18 @@ async fn listen_to_websocket( } async fn run_job( - db: DB, + db: &DB, rsmq: Option, - trigger: WebsocketTrigger, + trigger: &WebsocketTrigger, msg: String, ) -> anyhow::Result<()> { let args = PushArgsOwned { args: HashMap::from([("msg".to_string(), to_raw_value(&msg))]), extra: Some(HashMap::from([( "wm_trigger".to_string(), - to_raw_value(&serde_json::json!({"kind": "websocket"})), + to_raw_value( + &serde_json::json!({"kind": "websocket", "websocket": { "url": trigger.url }}), + ), )])), }; let label_prefix = Some(format!("ws-{}-", trigger.path)); @@ -643,7 +967,7 @@ async fn run_job( trigger.edited_by.clone(), trigger.email.clone(), &trigger.workspace_id, - &db, + db, "anonymous".to_string(), ) .await?; @@ -653,27 +977,27 @@ async fn run_job( let run_query = RunJobQuery::default(); if trigger.is_flow { - run_wait_result_flow_by_path_internal( - db, - run_query, - StripPath(trigger.script_path.to_owned()), + run_flow_by_path_inner( authed, - rsmq, + db.clone(), user_db, - args, + rsmq, trigger.workspace_id.clone(), + StripPath(trigger.script_path.to_owned()), + run_query, + args, label_prefix, ) .await?; } else { - run_wait_result_script_by_path_internal( - db, - run_query, - StripPath(trigger.script_path.to_owned()), + run_script_by_path_inner( authed, - rsmq, + db.clone(), user_db, + rsmq, trigger.workspace_id.clone(), + StripPath(trigger.script_path.to_owned()), + run_query, args, label_prefix, ) diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 9b66cc65ac..d357a5ec72 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -426,6 +426,8 @@ pub enum FlowModuleValue { hash: Option, #[serde(skip_serializing_if = "Option::is_none")] tag_override: Option, + #[serde(skip_serializing_if = "Option::is_none")] + is_trigger: Option, }, Flow { #[serde(default)] @@ -474,6 +476,8 @@ pub enum FlowModuleValue { concurrent_limit: Option, #[serde(skip_serializing_if = "Option::is_none")] concurrency_time_window_s: Option, + #[serde(skip_serializing_if = "Option::is_none")] + is_trigger: Option, }, Identity, } @@ -505,6 +509,7 @@ struct UntaggedFlowModuleValue { custom_concurrency_key: Option, concurrent_limit: Option, concurrency_time_window_s: Option, + is_trigger: Option, } impl<'de> Deserialize<'de> for FlowModuleValue { @@ -522,6 +527,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue { .ok_or_else(|| serde::de::Error::missing_field("path"))?, hash: untagged.hash, tag_override: untagged.tag_override, + is_trigger: untagged.is_trigger, }), "flow" => Ok(FlowModuleValue::Flow { input_transforms: untagged.input_transforms.unwrap_or_default(), @@ -574,6 +580,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue { custom_concurrency_key: untagged.custom_concurrency_key, concurrent_limit: untagged.concurrent_limit, concurrency_time_window_s: untagged.concurrency_time_window_s, + is_trigger: untagged.is_trigger, }), "identity" => Ok(FlowModuleValue::Identity), other => Err(serde::de::Error::unknown_variant( diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index b0420ca248..a48db22b1f 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -29,6 +29,7 @@ pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation pub const HUB_BASE_URL_SETTING: &str = "hub_base_url"; pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url"; pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels"; +pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui"; pub const DEV_INSTANCE_SETTING: &str = "dev_instance"; pub const JWT_SECRET_SETTING: &str = "jwt_secret"; pub const EMAIL_DOMAIN_SETTING: &str = "email_domain"; diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index ad96594ac8..8eb57cb236 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -86,6 +86,8 @@ lazy_static::lazy_static! { pub static ref METRICS_ENABLED: AtomicBool = AtomicBool::new(std::env::var("METRICS_PORT").is_ok() || std::env::var("METRICS_ADDR").is_ok()); pub static ref METRICS_DEBUG_ENABLED: AtomicBool = AtomicBool::new(false); + pub static ref CRITICAL_ALERT_MUTE_UI_ENABLED: AtomicBool = AtomicBool::new(false); + pub static ref BASE_URL: Arc> = Arc::new(RwLock::new("".to_string())); pub static ref IS_READY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); diff --git a/backend/windmill-common/src/s3_helpers.rs b/backend/windmill-common/src/s3_helpers.rs index 4547c29d51..715615f37f 100644 --- a/backend/windmill-common/src/s3_helpers.rs +++ b/backend/windmill-common/src/s3_helpers.rs @@ -109,10 +109,13 @@ pub struct S3AwsOidcResource { pub audience: Option, } -#[derive(Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone)] pub struct S3Object { pub s3: String, + #[serde(skip_serializing_if = "Option::is_none")] pub storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub filename: Option, } #[cfg(feature = "parquet")] diff --git a/backend/windmill-common/src/server.rs b/backend/windmill-common/src/server.rs index 9fb39ad18b..b0c768a86f 100644 --- a/backend/windmill-common/src/server.rs +++ b/backend/windmill-common/src/server.rs @@ -10,6 +10,7 @@ pub struct Smtp { pub port: u16, pub from: String, pub tls_implicit: Option, + pub disable_tls: Option, } #[derive(Serialize, Deserialize, PartialEq)] @@ -20,6 +21,7 @@ pub struct SmtpConfigOpt { pub smtp_port: Option, pub smtp_from: Option, pub smtp_tls_implicit: Option, + pub smtp_disable_tls: Option, } pub async fn load_smtp_config(db: &DB) -> error::Result> { @@ -39,6 +41,7 @@ pub async fn load_smtp_config(db: &DB) -> error::Result> { username, password, tls_implicit: config.smtp_tls_implicit, + disable_tls: config.smtp_disable_tls, port: config.smtp_port.unwrap_or(587), from: config .smtp_from @@ -60,6 +63,9 @@ pub async fn load_smtp_config(db: &DB) -> error::Result> { tls_implicit: std::env::var("SMTP_TLS_IMPLICIT") .ok() .and_then(|p| p.parse().ok()), + disable_tls: std::env::var("SMTP_DISABLE_TLS") + .ok() + .and_then(|p| p.parse().ok()), port: std::env::var("SMTP_PORT") .ok() .and_then(|p| p.parse().ok()) @@ -87,6 +93,7 @@ impl Default for SmtpConfigOpt { smtp_port: None, smtp_tls_implicit: None, smtp_username: None, + smtp_disable_tls: None, } } } diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 04f95a3406..18c0c93205 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -28,6 +28,9 @@ pub const DEFAULT_PER_PAGE: usize = 1000; pub const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version"); +use std::sync::atomic::Ordering; +use crate::CRITICAL_ALERT_MUTE_UI_ENABLED; + lazy_static::lazy_static! { pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new() .user_agent("windmill/beta") @@ -244,9 +247,12 @@ pub fn generate_lock_id(database_name: &str) -> i64 { pub async fn report_critical_error(error_message: String, _db: DB) -> () { tracing::error!("CRITICAL ERROR: {error_message}"); + let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed); + if let Err(err) = sqlx::query!( - "INSERT INTO alerts (alert_type, message) VALUES ('critical_error', $1)", - error_message + "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)", + error_message, + mute ) .execute(&_db) .await @@ -261,9 +267,12 @@ pub async fn report_critical_error(error_message: String, _db: DB) -> () { pub async fn report_recovered_critical_error(message: String, _db: DB) -> () { tracing::info!("RECOVERED CRITICAL ERROR: {message}"); + let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed); + if let Err(err) = sqlx::query!( - "INSERT INTO alerts (alert_type, message) VALUES ('recovered_critical_error', $1)", - message + "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)", + message, + mute ) .execute(&_db) .await diff --git a/backend/windmill-indexer/src/completed_runs_ee.rs b/backend/windmill-indexer/src/completed_runs_ee.rs new file mode 100644 index 0000000000..79bcbcff77 --- /dev/null +++ b/backend/windmill-indexer/src/completed_runs_ee.rs @@ -0,0 +1,21 @@ +use sqlx::{Pool, Postgres}; +use windmill_common::error::Error; +use anyhow::anyhow; + +#[derive(Clone)] +pub struct IndexReader; + +#[derive(Clone)] +pub struct IndexWriter; + +pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { + Err(anyhow!("Cannot initialize index: not in EE").into()) +} + +pub async fn run_indexer( + _db: Pool, + mut _index_writer: IndexWriter, + mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, +) { + tracing::error!("Cannot run indexer: not in EE"); +} diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs index da92ff0ef8..e69de29bb2 100644 --- a/backend/windmill-indexer/src/indexer_ee.rs +++ b/backend/windmill-indexer/src/indexer_ee.rs @@ -1,21 +0,0 @@ -use anyhow::anyhow; -use sqlx::{Pool, Postgres}; -use windmill_common::error::Error; - -#[derive(Clone)] -pub struct IndexReader; - -#[derive(Clone)] -pub struct IndexWriter; - -pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { - Err(anyhow!("Cannot initialize index: not in EE").into()) -} - -pub async fn run_indexer( - _db: Pool, - mut _index_writer: IndexWriter, - mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, -) { - tracing::error!("Cannot run indexer: not in EE"); -} diff --git a/backend/windmill-indexer/src/lib.rs b/backend/windmill-indexer/src/lib.rs index c1bf4eea0c..14c6e7906d 100644 --- a/backend/windmill-indexer/src/lib.rs +++ b/backend/windmill-indexer/src/lib.rs @@ -1 +1,3 @@ +pub mod completed_runs_ee; +pub mod service_logs_ee; pub mod indexer_ee; diff --git a/backend/windmill-indexer/src/service_logs_ee.rs b/backend/windmill-indexer/src/service_logs_ee.rs new file mode 100644 index 0000000000..79bcbcff77 --- /dev/null +++ b/backend/windmill-indexer/src/service_logs_ee.rs @@ -0,0 +1,21 @@ +use sqlx::{Pool, Postgres}; +use windmill_common::error::Error; +use anyhow::anyhow; + +#[derive(Clone)] +pub struct IndexReader; + +#[derive(Clone)] +pub struct IndexWriter; + +pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { + Err(anyhow!("Cannot initialize index: not in EE").into()) +} + +pub async fn run_indexer( + _db: Pool, + mut _index_writer: IndexWriter, + mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, +) { + tracing::error!("Cannot run indexer: not in EE"); +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index c0235c9b26..9b94c0f198 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3522,6 +3522,7 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>( path: path.clone(), hash: Some(hash), tag_override: tag_override, + is_trigger: None, }, ), stop_after_if: None, diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 2c4c46ab7d..2582f98b58 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -89,8 +89,18 @@ cat bp | tail -1 >> ./result2.out & # Run main.sh in the same process group {bash} ./main.sh "$@" 2>&1 | tee bp & -# Wait for all background processes to finish -wait +pid=$! + +# Wait for main.sh to finish and capture its exit status +wait $pid +exit_status=$? + +# Clean up the named pipe and background processes +rm -f bp + + +# Exit with the captured status +exit $exit_status "#, bash = BIN_BASH.as_str(), ); diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 5b7c2a06c0..a7d92874e1 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -1304,6 +1304,8 @@ try {{ job.args.as_ref() }; + append_logs(&job.id, &job.workspace_id, format!("{init_logs}\n"), db).await; + let result = crate::js_eval::eval_fetch_timeout( env_code, inner_content.clone(), @@ -1324,14 +1326,7 @@ try {{ "Executed native code in {}ms", started_at.elapsed().as_millis() ); - append_logs( - &job.id, - &job.workspace_id, - format!("{}\n{}", init_logs, result.1), - db, - ) - .await; - return Ok(result.0); + return Ok(result); } } append_logs(&job.id, &job.workspace_id, init_logs, db).await; diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index e22f9be54c..b406226120 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -562,7 +562,7 @@ pub async fn resolve_job_timeout( _w_id: &str, _job_id: Uuid, custom_timeout_secs: Option, -) -> (Duration, Option) { +) -> (Duration, Option, bool) { let mut warn_msg: Option = None; #[cfg(feature = "cloud")] let cloud_premium_workspace = *CLOUD_HOSTED @@ -587,12 +587,12 @@ pub async fn resolve_job_timeout( Some(timeout_secs) if Duration::from_secs(timeout_secs as u64) < global_max_timeout_duration => { - (Duration::from_secs(timeout_secs as u64), warn_msg) + (Duration::from_secs(timeout_secs as u64), warn_msg, true) } Some(timeout_secs) => { warn_msg = Some(format!("WARNING: Custom job timeout of {timeout_secs} seconds was greater than the maximum timeout. It will be ignored and the max timeout will be used instead")); tracing::warn!(warn_msg); - (global_max_timeout_duration, warn_msg) + (global_max_timeout_duration, warn_msg, false) } None => { // fallback to default timeout or max if not set @@ -610,7 +610,7 @@ pub async fn resolve_job_timeout( global_max_timeout_duration } }; - (default_timeout, warn_msg) + (default_timeout, warn_msg, false) } } } @@ -799,6 +799,7 @@ pub async fn get_cached_resource_value_if_valid( S3Object { s3: s3_file_key.clone(), storage: cached_resource.storage.clone(), + filename: None, }, ) .await; diff --git a/backend/windmill-worker/src/handle_child.rs b/backend/windmill-worker/src/handle_child.rs index 6e7520daf8..6846e9dfee 100644 --- a/backend/windmill-worker/src/handle_child.rs +++ b/backend/windmill-worker/src/handle_child.rs @@ -143,15 +143,40 @@ pub async fn handle_child( occupancy_metrics, ); - #[derive(PartialEq, Debug)] enum KillReason { TooManyLogs, - Timeout, - Cancelled, + Timeout { is_job_specific: bool }, + Cancelled(Option), AlreadyCompleted, } - let (timeout_duration, timeout_warn_msg) = + impl std::fmt::Debug for KillReason { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + KillReason::TooManyLogs => f.write_str("too many logs (max size: 2MB)"), + KillReason::Timeout { is_job_specific } => f.write_str(if *is_job_specific { + "timeout after exceeding job-specific duration limit" + } else { + "timeout after exceeding instance-wide job duration limit" + }), + KillReason::Cancelled(canceled_by) => { + let mut reason = "cancelled".to_string(); + if let Some(canceled_by) = canceled_by { + if let Some(by) = canceled_by.username.as_ref() { + reason.push_str(&format!(" by {}", by)); + } + if let Some(rsn) = canceled_by.reason.as_ref() { + reason.push_str(&format!(" (reason: {})", rsn)); + } + } + f.write_str(&reason) + } + KillReason::AlreadyCompleted => f.write_str("already completed"), + } + } + } + + let (timeout_duration, timeout_warn_msg, is_job_specific) = resolve_job_timeout(&db, w_id, job_id, custom_timeout).await; if let Some(msg) = timeout_warn_msg { append_logs(&job_id, w_id, msg.as_str(), db).await; @@ -165,9 +190,9 @@ pub async fn handle_child( biased; result = child.wait() => return result.map(Ok), Ok(()) = too_many_logs.changed() => KillReason::TooManyLogs, - _ = sleep(timeout_duration) => KillReason::Timeout, + _ = sleep(timeout_duration) => KillReason::Timeout { is_job_specific }, ex = update_job, if job_id != Uuid::nil() => match ex { - UpdateJobPollingExit::Done => KillReason::Cancelled, + UpdateJobPollingExit::Done(canceled_by) => KillReason::Cancelled(canceled_by), UpdateJobPollingExit::AlreadyCompleted => KillReason::AlreadyCompleted, }, }; @@ -175,7 +200,7 @@ pub async fn handle_child( drop(tx); let set_reason = async { - if kill_reason == KillReason::Timeout { + if matches!(kill_reason, KillReason::Timeout { .. }) { if let Err(err) = sqlx::query( r#" UPDATE queue @@ -402,7 +427,7 @@ pub async fn handle_child( Err(Error::AlreadyCompleted("Job already completed".to_string())) } _ => Err(Error::ExecutionErr(format!( - "job process killed because {kill_reason:#?}" + "job process terminated due to {kill_reason:#?}" ))), }, Err(err) => Err(Error::ExecutionErr(format!("job process io error: {err}"))), @@ -505,7 +530,10 @@ where })?, ex = update_job, if job_id != Uuid::nil() => { match ex { - UpdateJobPollingExit::Done => Err(Error::ExecutionErr("Job cancelled".to_string())).map_err(to_anyhow)?, + UpdateJobPollingExit::Done(canceled_by) => { + let (by, reason) = canceled_by.as_ref().map_or(("unknown".to_string(), "unknown".to_string()), |x| (x.username.clone().unwrap_or("".to_string()), x.reason.clone().unwrap_or("".to_string()))); + Err(Error::ExecutionErr(format!("Job cancelled by {by} (reason: {reason})",))).map_err(to_anyhow)? + }, UpdateJobPollingExit::AlreadyCompleted => Err(Error::AlreadyCompleted("Job already completed".to_string())).map_err(to_anyhow)?, } } @@ -515,7 +543,7 @@ where } pub enum UpdateJobPollingExit { - Done, + Done(Option), AlreadyCompleted, } @@ -640,7 +668,7 @@ where } tracing::info!("job {job_id} finished"); - UpdateJobPollingExit::Done + UpdateJobPollingExit::Done(canceled_by_ref.clone()) } /// takes stdout and stderr from Child, panics if either are not present diff --git a/backend/windmill-worker/src/job_logger.rs b/backend/windmill-worker/src/job_logger.rs index 05b9e5812b..8a6d824e6e 100644 --- a/backend/windmill-worker/src/job_logger.rs +++ b/backend/windmill-worker/src/job_logger.rs @@ -1,15 +1,6 @@ -use itertools::Itertools; -use swc_ecma_parser::lexer::util::CharExt; - -#[cfg(all(feature = "enterprise", feature = "parquet"))] -use object_store::path::Path; use regex::Regex; -#[cfg(all(feature = "enterprise", feature = "parquet"))] -use windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS; - -use windmill_common::error::{self}; -use windmill_common::worker::{CLOUD_HOSTED, TMP_DIR}; +use windmill_common::worker::CLOUD_HOSTED; use windmill_queue::append_logs; @@ -19,6 +10,12 @@ use std::sync::Arc; use uuid::Uuid; use windmill_common::DB; +use crate::job_logger_ee::default_disk_log_storage; + +#[cfg(all(feature = "enterprise", feature = "parquet"))] +use crate::job_logger_ee::s3_storage; + + pub enum CompactLogs { #[cfg(not(all(feature = "enterprise", feature = "parquet")))] NotEE, @@ -28,150 +25,7 @@ pub enum CompactLogs { S3, } -async fn compact_logs( - job_id: Uuid, - w_id: &str, - db: &DB, - nlogs: String, - total_size: Arc, - compact_kind: CompactLogs, - _worker_name: &str, -) -> error::Result<(String, String)> { - let mut prev_logs = sqlx::query_scalar!( - "SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = $2", - job_id, - w_id - ) - .fetch_optional(db) - .await? - .flatten() - .unwrap_or_default(); - let size = prev_logs.char_indices().count() as i32; - let nlogs_len = nlogs.char_indices().count(); - let to_keep_in_db = usize::max( - usize::min(nlogs_len, 3000), - nlogs_len % LARGE_LOG_THRESHOLD_SIZE, - ); - let extra_split = to_keep_in_db < nlogs_len; - let stored_in_storage_len = if extra_split { - nlogs_len - to_keep_in_db - } else { - 0 - }; - let extra_to_newline = nlogs - .chars() - .skip(stored_in_storage_len) - .find_position(|x| x.is_line_break()) - .map(|(i, _)| i) - .unwrap_or(to_keep_in_db); - let stored_in_storage_to_newline = stored_in_storage_len + extra_to_newline; - let (append_to_storage, stored_in_db) = if extra_split { - if stored_in_storage_to_newline == nlogs.len() { - (nlogs.as_ref(), "".to_string()) - } else { - let split_idx = nlogs - .char_indices() - .nth(stored_in_storage_to_newline) - .map(|(i, _)| i) - .unwrap_or(0); - let (append_to_storage, stored_in_db) = nlogs.split_at(split_idx); - // tracing::error!("{append_to_storage} ||||| {stored_in_db}"); - // tracing::error!( - // "{:?} {:?} {} {}", - // excess_prev_logs.lines().last(), - // current_logs.lines().next(), - // split_idx, - // excess_size_modulo - // ); - (append_to_storage, stored_in_db.to_string()) - } - } else { - // tracing::error!("{:?}", nlogs.lines().last()); - ("", nlogs.to_string()) - }; - - let new_size_with_excess = size + stored_in_storage_to_newline as i32; - - let new_size = total_size.fetch_add( - new_size_with_excess as u32, - std::sync::atomic::Ordering::SeqCst, - ) + new_size_with_excess as u32; - - let path = format!( - "logs/{job_id}/{}_{new_size}.txt", - chrono::Utc::now().timestamp_millis() - ); - - let mut new_current_logs = match compact_kind { - CompactLogs::NoS3 => format!("\n[windmill] No object storage set in instance settings. Previous logs have been saved to disk at {path}"), - CompactLogs::S3 => format!("\n[windmill] Previous logs have been saved to object storage at {path}"), - #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - CompactLogs::NotEE => format!("\n[windmill] Previous logs have been saved to disk at {path}"), - }; - new_current_logs.push_str(&stored_in_db); - - sqlx::query!( - "UPDATE job_logs SET logs = $1, log_offset = $2, - log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3) - WHERE workspace_id = $4 AND job_id = $5", - new_current_logs, - new_size as i32, - path, - w_id, - job_id - ) - .execute(db) - .await?; - prev_logs.push_str(&append_to_storage); - - return Ok((prev_logs, path)); -} - -async fn default_disk_log_storage( - job_id: Uuid, - w_id: &str, - db: &DB, - nlogs: String, - total_size: Arc, - compact_kind: CompactLogs, - worker_name: &str, -) { - match compact_logs( - job_id, - &w_id, - &db, - nlogs, - total_size, - compact_kind, - worker_name, - ) - .await - { - Err(e) => tracing::error!("Could not compact logs for job {job_id}: {e:?}",), - Ok((prev_logs, path)) => { - let path = format!("{}/{}", TMP_DIR, path); - let splitted = &path.split("/").collect_vec(); - tokio::fs::create_dir_all(splitted.into_iter().take(splitted.len() - 1).join("/")) - .await - .map_err(|e| { - tracing::error!("Could not create logs directory: {e:?}",); - e - }) - .ok(); - let created = tokio::fs::File::create(&path).await; - if let Err(e) = created { - tracing::error!("Could not create logs file {path}: {e:?}",); - return; - } - if let Err(e) = tokio::fs::write(&path, prev_logs).await { - tracing::error!("Could not write to logs file {path}: {e:?}"); - } else { - tracing::info!("Logs length of {job_id} has exceeded a threshold. Previous logs have been saved to disk at {path}"); - } - } - } -} pub(crate) async fn append_job_logs( job_id: Uuid, @@ -184,43 +38,7 @@ pub(crate) async fn append_job_logs( ) -> () { if must_compact_logs { #[cfg(all(feature = "enterprise", feature = "parquet"))] - if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { - match compact_logs( - job_id, - &w_id, - &db, - logs, - total_size, - CompactLogs::S3, - &worker_name, - ) - .await - { - Err(e) => tracing::error!("Could not compact logs for job {job_id}: {e:?}",), - Ok((prev_logs, path)) => { - tracing::info!("Logs length of {job_id} has exceeded a threshold. Previous logs have been saved to object storage at {path}"); - let path2 = path.clone(); - if let Err(e) = os - .put(&Path::from(path), prev_logs.to_string().into_bytes().into()) - .await - { - tracing::error!("Could not save logs to s3: {e:?}"); - } - tracing::info!("Logs of {job_id} saved to object storage at {path2}"); - } - } - } else { - default_disk_log_storage( - job_id, - &w_id, - &db, - logs, - total_size, - CompactLogs::NoS3, - &worker_name, - ) - .await; - } + s3_storage(job_id, &w_id, &db, &logs, &total_size, &worker_name).await; #[cfg(not(all(feature = "enterprise", feature = "parquet")))] { diff --git a/backend/windmill-worker/src/job_logger_ee.rs b/backend/windmill-worker/src/job_logger_ee.rs new file mode 100644 index 0000000000..0274c6c777 --- /dev/null +++ b/backend/windmill-worker/src/job_logger_ee.rs @@ -0,0 +1,26 @@ +use std::sync::atomic::AtomicU32; +use std::sync::Arc; + +use uuid::Uuid; +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, _logs: &String, _total_size: &Arc, _worker_name: &String) { + tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on s3 in not OSS"); +} + +pub(crate) async fn default_disk_log_storage( + job_id: Uuid, + _w_id: &str, + _db: &DB, + _nlogs: String, + _total_size: Arc, + _compact_kind: CompactLogs, + _worker_name: &str, +) { + tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on disk in not OSS"); +} + + diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index da4eda39ef..45ee1cefd2 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -754,9 +754,9 @@ pub async fn eval_fetch_timeout( _w_id: &str, _load_client: bool, _occupation_metrics: &mut OccupancyMetrics, -) -> anyhow::Result<(Box, String)> { +) -> anyhow::Result> { use serde_json::value::to_raw_value; - Ok((to_raw_value("require deno_core").unwrap(), "".to_string())) + Ok(to_raw_value("require deno_core").unwrap()) } #[cfg(feature = "deno_core")] @@ -774,7 +774,9 @@ pub async fn eval_fetch_timeout( w_id: &str, load_client: bool, occupation_metrics: &mut OccupancyMetrics, -) -> anyhow::Result<(Box, String)> { +) -> anyhow::Result> { + use windmill_queue::append_logs; + let (sender, mut receiver) = oneshot::channel::(); let parsed_args = windmill_parser_ts::parse_deno_signature(&ts_expr, true, None)?.args; @@ -805,6 +807,8 @@ pub async fn eval_fetch_timeout( )); } + let db_ = db.clone(); + let w_id_ = w_id.to_string(); let result_f = tokio::task::spawn_blocking(move || { let ops = vec![op_get_static_args(), op_log()]; let ext = Extension { name: "windmill", ops: ops.into(), ..Default::default() }; @@ -892,28 +896,31 @@ pub async fn eval_fetch_timeout( .build()?; let future = async { - tokio::select! { + let r = tokio::select! { r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), load_client) => Ok(r), _ = memory_limit_rx.recv() => Err(Error::ExecutionErr("Memory limit reached, killing isolate".to_string())) - } + }; + + append_logs( + &job_id, + w_id_.as_str(), + format!( + "{extra_logs}{}", + js_runtime.op_state().borrow().borrow::().s + ), + db_, + ) + .await; + + r }; let r = runtime.block_on(future)?; // tracing::info!("total: {:?}", instant.elapsed()); - (r as anyhow::Result>).map(|x| { - ( - x, - js_runtime - .op_state() - .borrow() - .borrow::() - .s - .clone(), - ) - }) + r }); - let (res, logs) = run_future_with_polling_update_job_poller( + let res = run_future_with_polling_update_job_poller( job_id, job_timeout, db, @@ -932,7 +939,7 @@ pub async fn eval_fetch_timeout( e })?; *mem_peak = (res.get().len() / 1000) as i32; - Ok((res, format!("{extra_logs}{logs}"))) + Ok(res) } #[cfg(feature = "deno_core")] diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index b790eb3faf..680f3ca178 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -29,7 +29,7 @@ mod rust_executor; mod worker; mod worker_flow; mod worker_lockfiles; - +mod job_logger_ee; pub use worker::*; pub use result_processor::handle_job_error; diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 0e68e2867d..7531360ddc 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -205,8 +205,11 @@ pub async fn uv_pip_compile( .clone() .map(handle_ephemeral_token); if let Some(url) = pip_extra_index_url.as_ref() { - args.extend(["--extra-index-url", url, "--no-emit-index-url"]); - pip_args.push(format!("--extra-index-url {}", url)); + url.split(",").for_each(|url| { + args.extend(["--extra-index-url", url]); + pip_args.push(format!("--extra-index-url {}", url)); + }); + args.push("--no-emit-index-url"); } let pip_index_url = PIP_INDEX_URL .read() @@ -285,7 +288,9 @@ pub async fn uv_pip_compile( .clone() .map(handle_ephemeral_token); if let Some(url) = pip_extra_index_url.as_ref() { - args.extend(["--extra-index-url", url]); + url.split(",").for_each(|url| { + args.extend(["--extra-index-url", url]); + }); } let pip_index_url = PIP_INDEX_URL .read() @@ -1230,7 +1235,9 @@ pub async fn handle_python_reqs( .map(handle_ephemeral_token); if let Some(url) = pip_extra_index_url.as_ref() { - command_args.extend(["--extra-index-url", url]); + url.split(",").for_each(|url| { + command_args.extend(["--extra-index-url", url]); + }); } let pip_index_url = PIP_INDEX_URL .read() diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index 6c4f43746f..fa08fe377b 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -295,6 +295,7 @@ pub async fn process_result( message: format!("error during execution of the script:\n{}", err), name: "ExecutionErr".to_string(), step_id: job.flow_step_id.clone(), + exit_code: None, }), }; @@ -608,6 +609,8 @@ pub struct SerializedError { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub step_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exit_code: Option, } pub fn extract_error_value(log_lines: &str, i: i32, step_id: Option) -> Box { return to_raw_value(&SerializedError { @@ -617,5 +620,6 @@ pub fn extract_error_value(log_lines: &str, i: i32, step_id: Option) -> ), name: "ExecutionErr".to_string(), step_id, + exit_code: Some(i), }); } diff --git a/backend/windmill-worker/src/snowflake_executor.rs b/backend/windmill-worker/src/snowflake_executor.rs index f89b29832a..9f8d3e12fd 100644 --- a/backend/windmill-worker/src/snowflake_executor.rs +++ b/backend/windmill-worker/src/snowflake_executor.rs @@ -32,9 +32,9 @@ struct Claims { #[derive(Deserialize)] struct SnowflakeDatabase { account_identifier: String, - public_key: String, - private_key: String, - username: String, + public_key: Option, + private_key: Option, + username: Option, database: Option, schema: Option, warehouse: Option, @@ -119,6 +119,7 @@ fn do_snowflake_inner<'a>( mut body: serde_json::Map, account_identifier: &'a str, token: &'a str, + token_is_keypair: bool, column_order: Option<&'a mut Option>>, skip_collect: bool, ) -> windmill_common::error::Result>>> { @@ -144,16 +145,19 @@ fn do_snowflake_inner<'a>( } let result_f = async move { - let result = HTTP_CLIENT + let mut request = HTTP_CLIENT .post(format!( "https://{}.snowflakecomputing.com/api/v2/statements/", account_identifier.to_uppercase() )) .bearer_auth(token) - .header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT") - .json(&body) - .send() - .await; + .json(&body); + + if token_is_keypair { + request = request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); + } + + let result = request.send().await; if skip_collect { handle_snowflake_result(result).await?; @@ -189,11 +193,17 @@ fn do_snowflake_inner<'a>( account_identifier.to_uppercase(), response.statementHandle ); - let response = HTTP_CLIENT + let mut request = HTTP_CLIENT .get(url) .bearer_auth(token) - .header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT") - .query(&[("partition", idx.to_string())]) + .query(&[("partition", idx.to_string())]); + + if token_is_keypair { + request = + request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); + } + + let response = request .send() .await .parse_snowflake_response::() @@ -258,7 +268,7 @@ pub async fn do_snowflake( snowflake_args.get("database").cloned() }; - let database = if let Some(db) = db_arg { + let database = if let Some(ref db) = db_arg { serde_json::from_value::(db.clone()) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { @@ -267,37 +277,59 @@ pub async fn do_snowflake( let annotations = windmill_common::worker::SqlAnnotations::parse(query); - let qualified_username = format!( - "{}.{}", - database.account_identifier.split('.').next().unwrap_or(""), // get first part of account identifier - database.username - ) - .to_uppercase(); + // Check if the token is present in db_arg and use it if available + let (token, token_is_keypair) = if let Some(token) = db_arg + .as_ref() + .and_then(|db| db.get("token")) + .and_then(|t| t.as_str()) + .filter(|t| !t.is_empty()) + { + tracing::debug!("Using oauth token from db_arg"); + (token.to_string(), false) + } else { + tracing::debug!("Generating new oauth token"); - let public_key = pem::parse(database.public_key.as_bytes()).map_err(|e| { - Error::ExecutionErr(format!("Failed to parse public key: {}", e.to_string())) - })?; - let mut public_key_hash = Sha256::new(); - public_key_hash.update(public_key.contents()); + let qualified_username = format!( + "{}.{}", + database.account_identifier.split('.').next().unwrap_or(""), + database.username.as_deref().unwrap_or("") + ) + .to_uppercase(); - let public_key_fp = engine::general_purpose::STANDARD.encode(public_key_hash.finalize()); + let public_key = match database.public_key.as_deref() { + Some(key) => pem::parse(key.as_bytes()).map_err(|e| { + Error::ExecutionErr(format!("Failed to parse public key: {}", e.to_string())) + })?, + None => return Err(Error::ExecutionErr("Public key is missing".to_string())), + }; + let mut public_key_hash = Sha256::new(); + public_key_hash.update(public_key.contents()); - let iss = format!("{}.SHA256:{}", qualified_username, public_key_fp); + let public_key_fp = engine::general_purpose::STANDARD.encode(public_key_hash.finalize()); - let claims = Claims { - iss: iss, - sub: qualified_username, - iat: chrono::Utc::now().timestamp(), - exp: (chrono::Utc::now() + chrono::Duration::try_hours(1).unwrap()).timestamp(), + let iss = format!("{}.SHA256:{}", qualified_username, public_key_fp); + + let claims = Claims { + iss: iss, + sub: qualified_username, + iat: chrono::Utc::now().timestamp(), + exp: (chrono::Utc::now() + chrono::Duration::try_hours(1).unwrap()).timestamp(), + }; + + let private_key = match database.private_key.as_deref() { + Some(key) => EncodingKey::from_rsa_pem(key.as_bytes()).map_err(|e| { + Error::ExecutionErr(format!("Failed to parse private key: {}", e.to_string())) + })?, + None => return Err(Error::ExecutionErr("Private key is missing".to_string())), + }; + + ( + encode(&Header::new(Algorithm::RS256), &claims, &private_key) + .map_err(|e| Error::ExecutionErr(e.to_string()))?, + true, + ) }; - let private_key = EncodingKey::from_rsa_pem(database.private_key.as_bytes()).map_err(|e| { - Error::ExecutionErr(format!("Failed to parse private key: {}", e.to_string())) - })?; - - let token = encode(&Header::new(Algorithm::RS256), &claims, &private_key) - .map_err(|e| Error::ExecutionErr(e.to_string()))?; - tracing::debug!("Snowflake token: {}", token); let mut body = serde_json::Map::new(); @@ -344,6 +376,7 @@ pub async fn do_snowflake( body.clone(), &database.account_identifier, &token, + token_is_keypair, None, annotations.return_last_result && i < queries.len() - 1, ) @@ -371,6 +404,7 @@ pub async fn do_snowflake( body.clone(), &database.account_identifier, &token, + token_is_keypair, Some(column_order), false, )? diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index d554451d09..bdd37f49a3 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -1719,7 +1719,7 @@ async fn do_nativets( canceled_by: &mut Option, worker_name: &str, occupancy_metrics: &mut OccupancyMetrics, -) -> windmill_common::error::Result<(Box, String)> { +) -> windmill_common::error::Result> { let args = build_args_map(job, client, db).await?.map(Json); let job_args = if args.is_some() { args.as_ref() @@ -1727,7 +1727,7 @@ async fn do_nativets( job.args.as_ref() }; - let result = eval_fetch_timeout( + Ok(eval_fetch_timeout( env_code, code.clone(), transpile_ts(code)?, @@ -1742,8 +1742,7 @@ async fn do_nativets( true, occupancy_metrics, ) - .await?; - Ok((result.0, result.1)) + .await?) } #[derive(Deserialize, Serialize, Default)] @@ -2372,7 +2371,8 @@ async fn handle_code_execution_job( .map(|(k, v)| format!("const {} = '{}';\nprocess.env['{}'] = '{}';\n", k, v, k, v)) .collect::>() .join("\n")); - let (result, ts_logs) = do_nativets( + + let result = do_nativets( job, &client, env_code, @@ -2384,7 +2384,6 @@ async fn handle_code_execution_job( occupancy_metrics, ) .await?; - append_logs(&job.id, &job.workspace_id, ts_logs, db).await; return Ok(result); } diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 9283f599df..4829b8bbed 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -735,6 +735,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, } = e.get_value()? else { match e.get_value()? { @@ -970,6 +971,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, }); new_flow_modules.push(e); continue; @@ -992,6 +994,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, }); new_flow_modules.push(e); continue; diff --git a/benchmarks/benchmark_oneoff.ts b/benchmarks/benchmark_oneoff.ts index 24ba4e2c13..41321b3ec6 100644 --- a/benchmarks/benchmark_oneoff.ts +++ b/benchmarks/benchmark_oneoff.ts @@ -8,6 +8,7 @@ import { DenoLandProvider } from "https://deno.land/x/cliffy@v0.25.7/command/upg 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"; import { VERSION, createBenchScript, getFlowPayload, login } from "./lib.ts"; @@ -172,8 +173,8 @@ export async function main({ kind: "script", path: "f/benchmarks/" + kind, }); - } else if (["2steps"].includes(kind)) { - nStepsFlow = 2; + } else if (["2steps", "bigscriptinflow"].includes(kind)) { + nStepsFlow = kind == "2steps" ? 2 : 1; const payload = getFlowPayload(kind); body = JSON.stringify({ kind: "flow", @@ -194,6 +195,15 @@ export async function main({ kind: "script", path: kind.substr(7), }); + } else if (kind == "bigrawscript") { + noVerify = true; + body = JSON.stringify({ + kind: "rawscript", + rawscript: { + language: api.RawScript.language.BASH, + content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n", + }, + }); } else { throw new Error("Unknown script pattern " + kind); } @@ -246,7 +256,7 @@ export async function main({ } else { const elapsed = start ? Date.now() - start : 0; completedJobs = await getCompletedJobsCount(); - if (kind === "2steps" || kind.startsWith("flow:")) { + if (nStepsFlow > 0) { completedJobs = Math.floor(completedJobs / (nStepsFlow + 1)); } const avgThr = ((completedJobs / elapsed) * 1000).toFixed(2); diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 3a229dc1e8..372d5be32e 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.416.2"; +export const VERSION = "v1.423.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ @@ -235,6 +235,23 @@ export const getFlowPayload = (flowPattern: string): api.FlowPreview => { ], }, }; + } else if (flowPattern == "bigscriptinflow") { + return { + path: "bigscriptinflow", + args: {}, + value: { + modules: [ + { + value: { + input_transforms: {}, + language: api.RawScript.language.BASH, + type: "rawscript", + content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n", + }, + } + ], + } + }; } else { return { path: "2steps", diff --git a/benchmarks/suite_config.json b/benchmarks/suite_config.json index 568402ca2d..7619d4d3b9 100644 --- a/benchmarks/suite_config.json +++ b/benchmarks/suite_config.json @@ -30,5 +30,13 @@ { "kind": "nativets", "jobs": 2000 + }, + { + "kind": "bigrawscript", + "jobs": 10000 + }, + { + "kind": "bigscriptinflow", + "jobs": 10000 } ] \ No newline at end of file diff --git a/cli/instance.ts b/cli/instance.ts index 1e86282d1a..e80db19700 100644 --- a/cli/instance.ts +++ b/cli/instance.ts @@ -184,6 +184,7 @@ export type InstanceSyncOptions = { folderPerInstance?: boolean; yes?: boolean; prefix?: string; + prefixSettings?: boolean; }; export async function pickInstance( @@ -260,30 +261,33 @@ export async function pickInstance( return instance; } -async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { +async function instancePull(opts: InstanceSyncOptions) { const instance = await pickInstance(opts, true); log.info("Pulling instance-level changes"); log.info(`remote (${instance.name}) -> local`); + opts.prefix = opts.prefix ?? instance.prefix; let uChanges = 0; if (!opts.skipUsers) { - uChanges = (await pullInstanceUsers(true)) as number; + uChanges = (await pullInstanceUsers(opts, true)) as number; } let sChanges = 0; if (!opts.skipSettings) { - sChanges = (await pullInstanceSettings(true)) as number; + sChanges = (await pullInstanceSettings(opts, true)) as number; } let cChanges = 0; if (!opts.skipConfigs) { - cChanges = (await pullInstanceConfigs(true)) as number; + cChanges = (await pullInstanceConfigs(opts, true)) as number; } let gChanges = 0; if (!opts.skipGroups) { - gChanges = (await pullInstanceGroups(true)) as number; + gChanges = (await pullInstanceGroups(opts, true)) as number; } const totalChanges = uChanges + sChanges + cChanges + gChanges; + const rootDir = Deno.cwd(); + if (totalChanges > 0) { let confirm = true; if (opts.yes !== true) { @@ -294,17 +298,24 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { } if (confirm) { + if (uChanges > 0) { + if (opts.folderPerInstance && opts.prefixSettings) { + await Deno.mkdir(path.join(rootDir, opts.prefix), { + recursive: true, + }); + } + } if (!opts.skipUsers && uChanges > 0) { - await pullInstanceUsers(); + await pullInstanceUsers(opts); } if (!opts.skipSettings && sChanges > 0) { - await pullInstanceSettings(); + await pullInstanceSettings(opts); } if (!opts.skipConfigs && cChanges > 0) { - await pullInstanceConfigs(); + await pullInstanceConfigs(opts); } if (!opts.skipGroups && gChanges > 0) { - await pullInstanceGroups(); + await pullInstanceGroups(opts); } } } else { @@ -313,7 +324,6 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { if (opts.includeWorkspaces) { log.info("\nPulling all workspaces"); - const rootDir = Deno.cwd(); const localWorkspaces = await getLocalWorkspaces( rootDir, instance.prefix, @@ -391,7 +401,7 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { } } -async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { +async function instancePush(opts: InstanceSyncOptions) { let instances = await allInstances(); const instance = await pickInstance(opts, true); @@ -400,19 +410,19 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { let uChanges = 0; if (!opts.skipUsers) { - uChanges = (await pushInstanceUsers(true)) as number; + uChanges = (await pushInstanceUsers(opts, true)) as number; } let sChanges = 0; if (!opts.skipSettings) { - sChanges = (await pushInstanceSettings(true, opts.baseUrl)) as number; + sChanges = (await pushInstanceSettings(opts, true)) as number; } let cChanges = 0; if (!opts.skipConfigs) { - cChanges = (await pushInstanceConfigs(true)) as number; + cChanges = (await pushInstanceConfigs(opts, true)) as number; } let gChanges = 0; if (!opts.skipGroups) { - gChanges = (await pushInstanceGroups(true)) as number; + gChanges = (await pushInstanceGroups(opts, true)) as number; } const totalChanges = uChanges + sChanges + cChanges + gChanges; @@ -428,16 +438,16 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { if (confirm) { if (!opts.skipUsers && uChanges > 0) { - await pushInstanceUsers(); + await pushInstanceUsers(opts); } if (!opts.skipSettings && sChanges > 0) { - await pushInstanceSettings(false, opts.baseUrl); + await pushInstanceSettings(opts); } if (!opts.skipConfigs && cChanges > 0) { - await pushInstanceConfigs(); + await pushInstanceConfigs(opts); } if (!opts.skipGroups && gChanges > 0) { - await pushInstanceGroups(); + await pushInstanceGroups(opts); } } } else { @@ -565,13 +575,14 @@ async function getLocalWorkspaces( } if (folderPerInstance) { for await (const dir of Deno.readDir(rootDir + "/" + localPrefix)) { - const dirName = dir.name; - localWorkspaces.push({ - dir: localPrefix + "/" + dirName, - id: dirName, - }); + if (dir.isDirectory) { + const dirName = dir.name; + localWorkspaces.push({ + dir: localPrefix + "/" + dirName, + id: dirName, + }); + } } - log.info(localWorkspaces); } else { for await (const dir of Deno.readDir(rootDir)) { const dirName = dir.name; @@ -583,6 +594,9 @@ async function getLocalWorkspaces( } } } + log.info( + "Local workspaces found: " + localWorkspaces.map((x) => x.id).join(", ") + ); return localWorkspaces; } @@ -709,7 +723,10 @@ const command = new Command() "--prefix ", "Prefix of the local workspaces to pull, used to create the folders when using --include-workspaces" ) - + .option( + "--prefix-settings", + "Store instance yamls inside prefixed folders when using --prefix and --folder-per-instance" + ) .action(instancePull as any) .command("push") .description( @@ -730,6 +747,10 @@ const command = new Command() "--prefix ", "Prefix of the local workspaces folders to push" ) + .option( + "--prefix-settings", + "Store instance yamls inside prefixed folders when using --prefix and --folder-per-instance" + ) .action(instancePush as any) .command("whoami") .description("Display information about the currently logged-in user") diff --git a/cli/main.ts b/cli/main.ts index 562c3fde45..24fff7a4c2 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.416.2"; +export const VERSION = "1.423.1"; const command = new Command() .name("wmill") diff --git a/cli/settings.ts b/cli/settings.ts index f9dbac1fa6..e938cd364e 100644 --- a/cli/settings.ts +++ b/cli/settings.ts @@ -3,8 +3,8 @@ import { Confirm } from "./deps.ts"; import { colors } from "./deps.ts"; import { yamlParseFile } from "./deps.ts"; import { log } from "./deps.ts"; -import { compareInstanceObjects } from "./instance.ts"; -import { isSuperset } from "./types.ts"; +import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts"; +import { isSuperset, GlobalOptions } from "./types.ts"; import { deepEqual } from "./utils.ts"; import * as wmill from "./gen/services.gen.ts"; import { Config, GlobalSetting } from "./gen/types.gen.ts"; @@ -33,6 +33,22 @@ export interface SimplifiedSettings { name: string; } +const INSTANCE_SETTINGS_PATH = "instance_settings.yaml"; +let instanceSettingsPath = INSTANCE_SETTINGS_PATH; +async function checkInstanceSettingsPath(opts: InstanceSyncOptions) { + if (opts.prefix && opts.folderPerInstance && opts.prefixSettings) { + instanceSettingsPath = `${opts.prefix}/${INSTANCE_SETTINGS_PATH}`; + } +} + +const INSTANCE_CONFIGS_PATH = "instance_configs.yaml"; +let instanceConfigsPath = INSTANCE_CONFIGS_PATH; +async function checkInstanceConfigPath(opts: InstanceSyncOptions) { + if (opts.prefix && opts.folderPerInstance && opts.prefixSettings) { + instanceConfigsPath = `${opts.prefix}/${INSTANCE_CONFIGS_PATH}`; + } +} + export async function pushWorkspaceSettings( workspace: string, _path: string, @@ -113,10 +129,10 @@ export async function pushWorkspaceSettings( workspace, requestBody: localSettings.auto_invite_enabled ? { - operator: localSettings.auto_invite_as === "operator", - invite_all: true, - auto_add: localSettings.auto_invite_mode === "add", - } + operator: localSettings.auto_invite_as === "operator", + invite_all: true, + auto_add: localSettings.auto_invite_mode === "add", + } : {}, }); } catch (_) { @@ -128,10 +144,10 @@ export async function pushWorkspaceSettings( workspace, requestBody: localSettings.auto_invite_enabled ? { - operator: localSettings.auto_invite_as === "operator", - invite_all: false, - auto_add: localSettings.auto_invite_mode === "add", - } + operator: localSettings.auto_invite_as === "operator", + invite_all: false, + auto_add: localSettings.auto_invite_mode === "add", + } : {}, }); } @@ -156,7 +172,7 @@ export async function pushWorkspaceSettings( settings.error_handler_extra_args ) || localSettings.error_handler_muted_on_cancel !== - settings.error_handler_muted_on_cancel + settings.error_handler_muted_on_cancel ) { log.debug(`Updating error handler...`); await wmill.editErrorHandler({ @@ -260,32 +276,36 @@ export async function pushWorkspaceKey( } } -const INSTANCE_SETTINGS_PATH = "instance_settings.yaml"; - -export async function readInstanceSettings() { +export async function readInstanceSettings(opts: InstanceSyncOptions) { let localSettings: GlobalSetting[] = []; + await checkInstanceSettingsPath(opts); + try { - localSettings = (await yamlParseFile(INSTANCE_SETTINGS_PATH)) as GlobalSetting[]; + localSettings = (await yamlParseFile(instanceSettingsPath)) as GlobalSetting[]; } catch { - log.warn(`No ${INSTANCE_SETTINGS_PATH} found`); + log.warn(`No ${instanceSettingsPath} found`); } return localSettings; } - import { decrypt, encrypt } from "./local_encryption.ts"; -const SENSITIVE_FIELD: string[] = ["license_key", "jwt_secret"] +const SENSITIVE_FIELD: string[] = ["license_key", "jwt_secret"]; -async function processInstanceSettings(settings: GlobalSetting[], mode: "encode" | "decode"): Promise { +async function processInstanceSettings( + settings: GlobalSetting[], + mode: "encode" | "decode" +): Promise { const encKey = process.env.WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY; if (encKey) { - const res: GlobalSetting[] = [] + const res: GlobalSetting[] = []; for (const s of settings) { if (SENSITIVE_FIELD.includes(s.name) && typeof s.value === "string") { - res.push(await processField(s, "value", encKey, mode) as GlobalSetting); + res.push( + (await processField(s, "value", encKey, mode)) as GlobalSetting + ); } else if (s.name == "oauths") { if (typeof s.value === "object") { const oauths = s.value as { [key: string]: any }; @@ -303,24 +323,42 @@ async function processInstanceSettings(settings: GlobalSetting[], mode: "encode" } return res; } else { - log.warn("No encryption key found, skipping encryption. Recommend setting WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY"); + log.warn( + "No encryption key found, skipping encryption. Recommend setting WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY" + ); } return settings; } -async function processField(obj: { [key: string]: any }, field: string, encKey: string, mode: "encode" | "decode"): Promise<{ [key: string]: any }> { +async function processField( + obj: { [key: string]: any }, + field: string, + encKey: string, + mode: "encode" | "decode" +): Promise<{ [key: string]: any }> { return { ...obj, - [field]: mode === "encode" ? await encrypt(obj[field], encKey) : await decrypt(obj[field], encKey) as any, - } + [field]: + mode === "encode" + ? await encrypt(obj[field], encKey) + : ((await decrypt(obj[field], encKey)) as any), + }; } -export async function pullInstanceSettings(preview = false) { +export async function pullInstanceSettings( + opts: InstanceSyncOptions, + preview = false +) { const remoteSettings = await wmill.listGlobalSettings(); + await checkInstanceSettingsPath(opts); + if (preview) { - const localSettings: GlobalSetting[] = await readInstanceSettings(); - const processedSettings = await processInstanceSettings(remoteSettings, "encode"); + const localSettings: GlobalSetting[] = await readInstanceSettings(opts); + const processedSettings = await processInstanceSettings( + remoteSettings, + "encode" + ); return compareInstanceObjects( processedSettings, localSettings, @@ -330,30 +368,33 @@ export async function pullInstanceSettings(preview = false) { } else { log.info("Pulling settings from instance"); - const processedSettings = await processInstanceSettings(remoteSettings, "encode"); + const processedSettings = await processInstanceSettings( + remoteSettings, + "encode" + ); await Deno.writeTextFile( - INSTANCE_SETTINGS_PATH, + instanceSettingsPath, yamlStringify(processedSettings) ); - log.info(colors.green(`Settings written to ${INSTANCE_SETTINGS_PATH}`)); + log.info(colors.green(`Settings written to ${instanceSettingsPath}`)); } } export async function pushInstanceSettings( - preview: boolean = false, - baseUrl?: string + opts: InstanceSyncOptions, + preview: boolean = false ) { const remoteSettings = await wmill.listGlobalSettings(); - let localSettings: GlobalSetting[] = await readInstanceSettings(); + let localSettings: GlobalSetting[] = await readInstanceSettings(opts); localSettings = await processInstanceSettings(localSettings, "decode"); - if (baseUrl) { + if (opts.baseUrl) { localSettings = localSettings.filter((s) => s.name !== "base_url"); localSettings.push({ name: "base_url", //@ts-ignore - value: baseUrl, + value: opts.baseUrl, }); } @@ -404,18 +445,25 @@ export async function pushInstanceSettings( } } -export async function readLocalConfigs() { +export async function readLocalConfigs( + opts: InstanceSyncOptions +) { let localConfigs: Config[] = []; + await checkInstanceConfigPath(opts); + try { - localConfigs = (await yamlParseFile("instance_configs.yaml")) as Config[]; + localConfigs = (await yamlParseFile(instanceConfigsPath)) as Config[]; } catch { - log.warn("No instance_configs.yaml found"); + log.warn(`No ${instanceConfigsPath} found`); } return localConfigs; } -export async function pullInstanceConfigs(preview = false) { +export async function pullInstanceConfigs( + opts: InstanceSyncOptions, + preview = false +) { const remoteConfigs = (await wmill.listConfigs()).map((x) => { return { ...x, @@ -424,7 +472,7 @@ export async function pullInstanceConfigs(preview = false) { }); if (preview) { - const localConfigs: Config[] = await readLocalConfigs(); + const localConfigs: Config[] = await readLocalConfigs(opts); return compareInstanceObjects( remoteConfigs, @@ -436,22 +484,25 @@ export async function pullInstanceConfigs(preview = false) { log.info("Pulling configs from instance"); await Deno.writeTextFile( - "instance_configs.yaml", + instanceConfigsPath, yamlStringify(remoteConfigs as any) ); - log.info(colors.green("Configs written to instance_configs.yaml")); + log.info(colors.green(`Configs written to ${instanceConfigsPath}`)); } } -export async function pushInstanceConfigs(preview: boolean = false) { +export async function pushInstanceConfigs( + opts: InstanceSyncOptions, + preview: boolean = false +) { const remoteConfigs = (await wmill.listConfigs()).map((x) => { return { ...x, name: removeWorkerPrefix(x.name), }; }); - const localConfigs = await readLocalConfigs(); + const localConfigs = await readLocalConfigs(opts); if (preview) { return compareInstanceObjects( diff --git a/cli/sync.ts b/cli/sync.ts index 342dda3162..f0ec3dce2c 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -1398,11 +1398,12 @@ export async function push(opts: GlobalOptions & SyncOptions) { log.info(`Deleting ${typ} ${change.path}`); } const workspaceId = workspace.workspaceId; + const target = change.path.replaceAll(SEP, "/"); switch (typ) { case "script": { const script = await wmill.getScriptByPath({ workspace: workspaceId, - path: removeExtensionToPath(change.path), + path: removeExtensionToPath(target), }); await wmill.archiveScriptByHash({ workspace: workspaceId, @@ -1419,37 +1420,37 @@ export async function push(opts: GlobalOptions & SyncOptions) { case "resource": await wmill.deleteResource({ workspace: workspaceId, - path: removeSuffix(change.path, ".resource.json"), + path: removeSuffix(target, ".resource.json"), }); break; case "resource-type": await wmill.deleteResourceType({ workspace: workspaceId, - path: removeSuffix(change.path, ".resource-type.json"), + path: removeSuffix(target, ".resource-type.json"), }); break; case "flow": await wmill.deleteFlowByPath({ workspace: workspaceId, - path: removeSuffix(change.path, ".flow/flow.json"), + path: removeSuffix(target, ".flow/flow.json"), }); break; case "app": await wmill.deleteApp({ workspace: workspaceId, - path: removeSuffix(change.path, ".app/app.json"), + path: removeSuffix(target, ".app/app.json"), }); break; case "schedule": await wmill.deleteSchedule({ workspace: workspaceId, - path: removeSuffix(change.path, ".schedule.json"), + path: removeSuffix(target, ".schedule.json"), }); break; case "variable": await wmill.deleteVariable({ workspace: workspaceId, - path: removeSuffix(change.path, ".variable.json"), + path: removeSuffix(target, ".variable.json"), }); break; case "user": { diff --git a/cli/user.ts b/cli/user.ts index 2ddeb997c9..fcd979222b 100644 --- a/cli/user.ts +++ b/cli/user.ts @@ -6,7 +6,7 @@ import { removeType, removePathPrefix, } from "./types.ts"; -import { compareInstanceObjects } from "./instance.ts"; +import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts"; import { colors, Command, @@ -23,6 +23,22 @@ import { InstanceGroup, } from "./gen/types.gen.ts"; +const INSTANCE_USERS_PATH = "instance_users.yaml"; +let instanceUsersPath = INSTANCE_USERS_PATH; +function checkInstanceUsersPath(opts: InstanceSyncOptions) { + if (opts.prefix && opts.folderPerInstance && opts.prefixSettings) { + instanceUsersPath = `${opts.prefix}/${INSTANCE_USERS_PATH}`; + } +} + +const INSTANCE_GROUPS_PATH = "instance_groups.yaml"; +let instanceGroupsPath = INSTANCE_GROUPS_PATH; +function checkInstanceGroupsPath(opts: InstanceSyncOptions) { + if (opts.prefix && opts.folderPerInstance && opts.prefixSettings) { + instanceGroupsPath = `${opts.prefix}/${INSTANCE_GROUPS_PATH}`; + } +} + async function list(opts: GlobalOptions) { await requireLogin(opts); @@ -386,47 +402,61 @@ export async function pushGroup( } } -export async function pullInstanceUsers(preview: boolean = false) { +export async function pullInstanceUsers( + opts: InstanceSyncOptions, + preview: boolean = false +) { const remoteUsers = await wmill.globalUsersExport(); + checkInstanceUsersPath(opts); + if (preview) { - const localUsers: ExportedUser[] = await readInstanceUsers(); + const localUsers: ExportedUser[] = await readInstanceUsers(opts); return compareInstanceObjects(remoteUsers, localUsers, "email", "user"); } else { log.info("Pulling users from instance..."); await Deno.writeTextFile( - "instance_users.yaml", + instanceUsersPath, yamlStringify(remoteUsers as any) ); - log.info(colors.green("Users written to instance_users.yaml")); + log.info(colors.green(`Users written to ${instanceUsersPath}`)); } } -export async function readInstanceUsers() { +export async function readInstanceUsers(opts: InstanceSyncOptions) { let localUsers: ExportedUser[] = []; + + await checkInstanceUsersPath(opts); + try { - localUsers = (await yamlParseFile("instance_users.yaml")) as ExportedUser[]; + localUsers = (await yamlParseFile(instanceUsersPath)) as ExportedUser[]; } catch { - log.warn("No instance_users.yaml file found"); + log.warn(`No ${instanceUsersPath} file found`); } return localUsers; } -export async function readInstanceGroups() { +export async function readInstanceGroups(opts: InstanceSyncOptions) { let localGroups: InstanceGroup[] = []; + + checkInstanceGroupsPath(opts); + try { localGroups = (await yamlParseFile( - "instance_groups.yaml" + instanceGroupsPath )) as ExportedInstanceGroup[]; } catch { - log.warn("No instance_groups.yaml file found"); + log.warn(`No ${instanceGroupsPath} file found`); } return localGroups; } -export async function pushInstanceUsers(preview: boolean = false) { +export async function pushInstanceUsers( + opts: InstanceSyncOptions, + preview: boolean = false +) { const remoteUsers = await wmill.globalUsersExport(); - const localUsers: ExportedUser[] = await readInstanceUsers(); + const localUsers: ExportedUser[] = await readInstanceUsers(opts); if (preview) { return compareInstanceObjects(localUsers, remoteUsers, "email", "user"); @@ -440,27 +470,35 @@ export async function pushInstanceUsers(preview: boolean = false) { } } -export async function pullInstanceGroups(preview = false) { +export async function pullInstanceGroups( + opts: InstanceSyncOptions, + preview = false +) { const remoteGroups = await wmill.exportInstanceGroups(); + checkInstanceGroupsPath(opts); + if (preview) { - const localGroups = await readInstanceGroups(); + const localGroups = await readInstanceGroups(opts); return compareInstanceObjects(remoteGroups, localGroups, "name", "group"); } else { log.info("Pulling groups from instance..."); await Deno.writeTextFile( - "instance_groups.yaml", + instanceGroupsPath, yamlStringify(remoteGroups as any) ); - log.info(colors.green("Groups written to instance_groups.yaml")); + log.info(colors.green(`Groups written to ${instanceGroupsPath}`)); } } -export async function pushInstanceGroups(preview: boolean = false) { +export async function pushInstanceGroups( + opts: InstanceSyncOptions, + preview: boolean = false +) { const remoteGroups = await wmill.exportInstanceGroups(); - const localGroups = await readInstanceGroups(); + const localGroups = await readInstanceGroups(opts); if (preview) { return compareInstanceObjects(localGroups, remoteGroups, "name", "group"); diff --git a/cli/worker_groups.ts b/cli/worker_groups.ts index bd1eb6f56f..17ff13ec40 100644 --- a/cli/worker_groups.ts +++ b/cli/worker_groups.ts @@ -56,7 +56,7 @@ export async function displayWorkerGroups(opts: void) { async function pullWorkerGroups(opts: InstanceSyncOptions) { await pickInstance(opts, true); - const totalChanges = await pullInstanceConfigs(true) ?? 0; + const totalChanges = await pullInstanceConfigs(opts, true) ?? 0; if (totalChanges === 0) { log.info("No changes to apply"); @@ -72,14 +72,14 @@ async function pullWorkerGroups(opts: InstanceSyncOptions) { } if (confirm) { - await pullInstanceConfigs(false); + await pullInstanceConfigs(opts, false); } } async function pushWorkerGroups(opts: InstanceSyncOptions) { await pickInstance(opts, true); - const totalChanges = await pushInstanceConfigs(true) ?? 0; + const totalChanges = await pushInstanceConfigs(opts, true) ?? 0; if (totalChanges === 0) { log.info("No changes to apply"); @@ -95,7 +95,7 @@ async function pushWorkerGroups(opts: InstanceSyncOptions) { } if (confirm) { - await pushInstanceConfigs(false); + await pushInstanceConfigs(opts, false); } } diff --git a/cli/workspace.ts b/cli/workspace.ts index 4338e21cfe..cd0ff31ee9 100644 --- a/cli/workspace.ts +++ b/cli/workspace.ts @@ -285,8 +285,8 @@ export async function removeWorkspace( } return; } - if (silent) { - log.info(colors.yellow(`Replacing existing workspace ${name}`)); + if (!silent) { + log.info(colors.yellow(`Removing existing workspace ${name}`)); } await Deno.writeTextFile( diff --git a/docker/DockerfileSlim b/docker/DockerfileSlim index 16c2361a0d..a1b5daca43 100644 --- a/docker/DockerfileSlim +++ b/docker/DockerfileSlim @@ -20,7 +20,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools # Install UV RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv -COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.34 /usr/local/bin/bun /usr/bin/bun # add the docker client to call docker from a worker if enabled COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ diff --git a/docker/DockerfileSlimEe b/docker/DockerfileSlimEe index 65c6b55a19..94cc160cae 100644 --- a/docker/DockerfileSlimEe +++ b/docker/DockerfileSlimEe @@ -19,7 +19,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools # Install UV RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv -COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.34 /usr/local/bin/bun /usr/bin/bun # add the docker client to call docker from a worker if enabled COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..5ad4516f5c --- /dev/null +++ b/flake.lock @@ -0,0 +1,95 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1730785428, + "narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1728538411, + "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1730946479, + "narHash": "sha256-AxGJ3BRc44o3RBcfXxZqjVYftVtJ2sl+/WEjiLUmXRY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "7fba269fe89ffad47206e0afba233d337c04cf08", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..28e0816667 --- /dev/null +++ b/flake.nix @@ -0,0 +1,141 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + buildInputs = with pkgs; [ + openssl openssl.dev libxml2.dev xmlsec.dev libxslt.dev + rust-bin.stable.latest.default nodejs + postgresql + pkg-config + ]; + PKG_CONFIG_PATH = pkgs.lib.makeSearchPath "lib/pkgconfig" (with pkgs; [ + openssl.dev + libxml2.dev + xmlsec.dev + libxslt.dev + ]); + in { + devShell = pkgs.mkShell { + buildInputs = buildInputs ++ (with pkgs; [ git go xcaddy sqlx-cli ]); + packages = [ + (pkgs.writeScriptBin "wm-caddy" '' + cd ./frontend + xcaddy build $* \ + --with github.com/mholt/caddy-l4@145ec36251a44286f05a10d231d8bfb3a8192e09 \ + --with github.com/RussellLuo/caddy-ext/layer4@ab1e18cfe426012af351a68463937ae2e934a2a1 + '') + (pkgs.writeScriptBin "wm-build" '' + cd ./frontend + echo $(pwd) + npm install + npm run ${if pkgs.stdenv.isDarwin then "generate-backend-client-mac" else "generate-backend-client"} + npm run build $* + '') + (pkgs.writeScriptBin "wm-migrate" '' + cd ./backend + sqlx migrate run + '') + (pkgs.writeScriptBin "wm-setup" '' + sqlx database create + wm-build + wm-caddy + wm-migrate + '') + (pkgs.writeScriptBin "wm-reset" '' + sqlx database drop -f + sqlx database create + wm-migrate + '') + (pkgs.writeScriptBin "wm" '' + cd ./frontend + npm run dev $* + '') + ]; + + inherit PKG_CONFIG_PATH; + NODE_ENV = "development"; + NODE_OPTIONS = "--max-old-space-size=16384"; + DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/"; + REMOTE = "http://127.0.0.1:8000"; + REMOTE_LSP = "http://127.0.0.1:3001"; + }; + packages.default = self.packages.${system}.windmill; + packages.windmill-client = pkgs.stdenv.mkDerivation { + pname = "windmill-client"; + version = (pkgs.lib.strings.trim (builtins.readFile ./version.txt)); + + src = ./.; + buildInputs = with pkgs; [ nodejs ]; + + buildPhase = '' + export HOME=$(pwd) + npm config set strict-ssl false + cd frontend + npm install --verbose + npm run generate-backend-client-mac + NODE_OPTIONS="--max-old-space-size=8192" npm run build + ''; + + installPhase = '' + mkdir -p $out/build + cp -r build $out/build + ''; + }; + packages.windmill = pkgs.rustPlatform.buildRustPackage { + pname = "windmill"; + version = (pkgs.lib.strings.trim (builtins.readFile ./version.txt)); + + src = ./.; + nativeBuildInputs = buildInputs ++ [ self.packages.${system}.windmill-client ]; + + cargoRoot = "backend"; + cargoLock = { + lockFile = ./backend/Cargo.lock; + outputHashes = { + "php-parser-rs-0.1.3" = "sha256-ZeI3KgUPmtjlRfq6eAYveqt8Ay35gwj6B9iOQRjQa9A="; + "progenitor-0.3.0" = "sha256-F6XRZFVIN6/HfcM8yI/PyNke45FL7jbcznIiqj22eIQ="; + "rustpython-ast-0.3.1" = "sha256-q9N+z3F6YICQuUMp3a10OS792tCq0GiSSlkcaLxi3Gs="; + "tiberius-0.12.2" = "sha256-s/S0K3hE+JNCrNVxoSCSs4myLHvukBYTwk2A5vZ7Ae8="; + "tinyvector-0.1.0" = "sha256-NYGhofU4rh+2IAM+zwe04YQdXY8Aa4gTmn2V2HtzRfI="; + }; + }; + + buildFeatures = [ ]; + doCheck = false; + preBuild = '' + export HOME=$(pwd) + npm config set strict-ssl false + cd backend + ''; + + inherit PKG_CONFIG_PATH; + SQLX_OFFLINE = true; + FRONTEND_BUILD_DIR = "${self.packages.${system}.windmill-client}/build"; + RUSTY_V8_ARCHIVE = + let + version = "130.0.1"; + target = pkgs.hostPlatform.rust.rustcTarget; + sha256 = { + x86_64-linux = pkgs.lib.fakeHash; + aarch64-linux = pkgs.lib.fakeHash; + x86_64-darwin = pkgs.lib.fakeHash; + aarch64-darwin = "sha256-d1QTLt8gOUFxACes4oyIYgDF/srLOEk+5p5Oj1ECajQ="; + }.${system}; + in pkgs.fetchurl { + name = "librusty_v8-${version}"; + url = "https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${target}.a.gz"; + inherit sha256; + }; + }; + }); +} diff --git a/frontend/README_DEV.md b/frontend/README_DEV.md index 198657d3af..ba90278be4 100644 --- a/frontend/README_DEV.md +++ b/frontend/README_DEV.md @@ -1,5 +1,7 @@ # Developing +[Using Nix](#nix), otherwise: + In the `frontend/` directory: - install the dependencies with `npm install` (or `pnpm install` or `yarn`) @@ -136,3 +138,45 @@ NOTCATCHALL=true npm run build which will generate an index.html and allow you to serve the frontend with any static server. Env variables used for build are set in .env file. See [https://vitejs.dev/guide/env-and-mode.html#env-files](https://vitejs.dev/guide/env-and-mode.html#env-files) for more details. + +## Nix + +Windmill use [nix flakes](https://nixos.wiki/wiki/Flakes): +```bash +nix run github:windmill-labs/windmill +``` + +### Development +```bash +nix develop # enter a dev shell containing all necessary packages. + +wm-setup # build the frontend and setup the database. +wm # run the frontend. + +# In an other shell: +nix develop +cd backend +cargo run +``` + +### Updating [`flake.nix`](../flake.nix) + +```bash +nix flake update # update the lock file. +``` + +Some cargo dependencies use fixed git revisions, which are also fixed in `flake.nix`: +```nix +outputHashes = { + "php-parser-rs-0.1.3" = "sha256-ZeI3KgUPmtjlRfq6eAYveqt8Ay35gwj6B9iOQRjQa9A="; + # ... +}; +``` + +When updating a revision, replace the incorrect `sha256` with `pkgs.lib.fakeHash`: +```diff +- "php-parser-rs-0.1.3" = "sha256-ZeI3KgUPmtjlRfq6eAYveqt8Ay35gwj6B9iOQRjQa9A="; ++ "php-parser-rs-0.1.3" = pkgs.lib.fakeHash; +``` + +Then run `nix build .#windmill` and update with the correct sha. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 028ac3e356..54c95638aa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.416.2", + "version": "1.423.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.416.2", + "version": "1.423.1", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", @@ -43,7 +43,7 @@ "graphql": "^16.7.1", "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", - "lucide-svelte": "^0.293.0", + "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~8.0.2", "monaco-editor-wrapper": "^5.5.2", @@ -8607,11 +8607,11 @@ "optional": true }, "node_modules/lucide-svelte": { - "version": "0.293.0", - "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.293.0.tgz", - "integrity": "sha512-nQ6QO6anjjiVoipw3dNptzI6NWuiArlhysVtRpoAWICSQVMR3ybwVf6piUUqbjGe+RfGufLzCl93C/yhsS+p4w==", + "version": "0.399.0", + "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.399.0.tgz", + "integrity": "sha512-NQ8AxNMKbIJsx7HV//gnAsIY1wJfb3rbXSK2S/ZDjIldvAEdzGngpUT8T8Q8zHYUuii0bavAmVARN8giR4vvpA==", "peerDependencies": { - "svelte": ">=3 <5" + "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "node_modules/magic-string": { diff --git a/frontend/package.json b/frontend/package.json index 9c4f3a2a7d..fe8ccedc24 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.416.2", + "version": "1.423.1", "scripts": { "dev": "vite dev", "build": "vite build", @@ -116,7 +116,7 @@ "graphql": "^16.7.1", "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", - "lucide-svelte": "^0.293.0", + "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~8.0.2", "monaco-editor-wrapper": "^5.5.2", @@ -182,6 +182,11 @@ "svelte": "./package/components/icons/WindmillIcon2.svelte", "default": "./package/components/icons/WindmillIcon2.svelte" }, + "./components/icons/SchedulePollIcon.svelte": { + "types": "./package/components/icons/SchedulePollIcon.d.ts", + "svelte": "./package/components/icons/SchedulePollIcon.svelte", + "default": "./package/components/icons/SchedulePollIcon.svelte" + }, "./components/IconedResourceType.svelte": { "types": "./package/components/IconedResourceType.svelte.d.ts", "svelte": "./package/components/IconedResourceType.svelte", diff --git a/frontend/src/lib/components/ApiConnectForm.svelte b/frontend/src/lib/components/ApiConnectForm.svelte index 1155be67ba..680b54850e 100644 --- a/frontend/src/lib/components/ApiConnectForm.svelte +++ b/frontend/src/lib/components/ApiConnectForm.svelte @@ -80,9 +80,8 @@ let connectionString = '' let validConnectionString = true function parseConnectionString(close: (_: any) => void) { - // parse postgres connection string const regex = - /postgres:\/\/(?[^:@]+)(?::(?[^@]+))?@(?[^:\/?]+)(?::(?\d+))?\/(?[^\?]+)?(?:\?.*sslmode=(?[^&]+))?/ + /postgres(?:ql)?:\/\/(?[^:@]+)(?::(?[^@]+))?@(?[^:\/?]+)(?::(?\d+))?\/(?[^\?]+)?(?:\?.*sslmode=(?[^&]+))?/ const match = connectionString.match(regex) if (match) { validConnectionString = true diff --git a/frontend/src/lib/components/AppConnectInner.svelte b/frontend/src/lib/components/AppConnectInner.svelte index e568589b49..8ea77cc12d 100644 --- a/frontend/src/lib/components/AppConnectInner.svelte +++ b/frontend/src/lib/components/AppConnectInner.svelte @@ -250,6 +250,14 @@ throw Error(`Resource at path ${path} already exists. Delete it or pick another path`) } + + if (resourceType == 'snowflake_oauth') { + const account_identifier = extra_params.find(([key, _]) => key == 'account_identifier') + if (account_identifier) { + args['account_identifier'] = account_identifier[1] + } + } + let account: number | undefined = undefined if (valueToken?.expires_in != undefined) { account = Number( diff --git a/frontend/src/lib/components/ArgInfo.svelte b/frontend/src/lib/components/ArgInfo.svelte index dc6f8e153d..16ba420372 100644 --- a/frontend/src/lib/components/ArgInfo.svelte +++ b/frontend/src/lib/components/ArgInfo.svelte @@ -98,7 +98,7 @@ }}> {/if} -
+
diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 80537e4d44..ef20ebdb35 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -128,8 +128,10 @@ let s3FilePicker: S3FilePicker let s3FileUploadRawMode: false let isListJson = false + let hasIsListJsonChanged = false let el: HTMLTextAreaElement | undefined = undefined + let inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding) $: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding) @@ -170,6 +172,43 @@ $: computeDefaultValue(value, inputCat, defaultValue, nullable) + let lastValue: any = undefined + + // By setting isListJson to true, we can render inputs even if the value is not an array of the correct type + // This avoids the issue of the input being rendered as a string with value: [object Object], or as a number with value: NaN + function checkArrayValueType() { + try { + if (Array.isArray(value) && value.length > 0) { + const firstItem = value?.[0] + const type = itemsType?.type + + switch (type) { + case 'string': + if (typeof firstItem !== 'string') { + isListJson = true + } + break + case 'number': + if (typeof firstItem !== 'number') { + isListJson = true + } + break + } + } + } catch (e) { + console.error(e) + } + + lastValue = value + } + + $: !isListJson && + inputCat === 'list' && + value != lastValue && + itemsType?.type && + !hasIsListJsonChanged && + checkArrayValueType() + $: defaultValue != undefined && handleDefaultValueChange() let oldDefaultValue = defaultValue @@ -379,6 +418,9 @@ bind:selected={value} options={itemsType?.multiselect ?? []} selectedOptionsDraggable={true} + on:open={() => { + dispatch('focus') + }} /> {:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)} @@ -389,6 +431,9 @@ bind:selected={value} options={itemsType?.enum ?? []} selectedOptionsDraggable={true} + on:open={() => { + dispatch('focus') + }} /> {:else} @@ -518,6 +563,11 @@
{ + // Once the user has changed the input type, we should not change it back automatically + if (!hasIsListJsonChanged) { + hasIsListJsonChanged = true + } + evalValueToRaw() isListJson = !isListJson }} @@ -772,6 +822,12 @@ bind:value {enum_} {autofocus} + on:focus={() => { + dispatch('focus') + }} + on:blur={(e) => { + dispatch('blur') + }} enumLabels={extra['enumLabels']} />
@@ -840,14 +896,16 @@
{#if password || extra?.['password'] == true} - {#if value && typeof value == 'string' && value?.startsWith('$var:')} - - {:else if onlyMaskPassword} - + {#if onlyMaskPassword} + {#if value && typeof value == 'string' && value?.startsWith('$var:')} + + {:else} + + {/if} {:else} {/if} diff --git a/frontend/src/lib/components/DeployWorkspace.svelte b/frontend/src/lib/components/DeployWorkspace.svelte index 71af286658..d06d330e1d 100644 --- a/frontend/src/lib/components/DeployWorkspace.svelte +++ b/frontend/src/lib/components/DeployWorkspace.svelte @@ -533,7 +533,7 @@ > {:else if notSet == true} As an admin, go to "Workspace {'->'} Dev/Staging/Prod"As an admin, go to Settings {'->'} Workspace {'->'} Deployment UI {:else} >({}) const selectedIdStore = writable('settings-metadata') const selectedTriggerStore = writable< - 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll' >('webhooks') const primaryScheduleStore = writable(undefined) @@ -484,7 +486,8 @@ setContext('TriggerContext', { primarySchedule: primaryScheduleStore, selectedTrigger: selectedTriggerStore, - triggersCount: triggersCount + triggersCount: triggersCount, + simplifiedPoll: writable(false) }) setContext('FlowEditorContext', { selectedId: selectedIdStore, @@ -502,7 +505,10 @@ customUi: {}, insertButtonOpen: writable(false) }) - + setContext('PropPickerContext', { + flowPropPickerConfig: writable(undefined), + pickablePropertiesFiltered: writable(undefined) + }) $: updateFlow($flowStore) let lastSent: OpenFlow | undefined = undefined diff --git a/frontend/src/lib/components/EditableSchemaForm.svelte b/frontend/src/lib/components/EditableSchemaForm.svelte index e85616319b..3bb5765ddc 100644 --- a/frontend/src/lib/components/EditableSchemaForm.svelte +++ b/frontend/src/lib/components/EditableSchemaForm.svelte @@ -349,6 +349,7 @@ {isAppInput} on:change={() => { schema = schema + // console.log('schema', schema) dispatch('change', schema) }} > diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index b04fbdf8dc..f51b7e8bc8 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -144,7 +144,7 @@ ? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } } : undefined ) - + const simplifiedPoll = writable(false) export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) { primaryScheduleStore.set(schedule) loadTriggers() @@ -468,7 +468,7 @@ const selectedIdStore = writable(selectedId ?? 'settings-metadata') const selectedTriggerStore = writable< - 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll' >('webhooks') export function getSelectedId() { @@ -490,7 +490,14 @@ } function selectTrigger( - selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + selectedTrigger: + | 'webhooks' + | 'emails' + | 'schedules' + | 'cli' + | 'routes' + | 'websockets' + | 'scheduledPoll' ) { selectedTriggerStore.set(selectedTrigger) } @@ -517,7 +524,8 @@ setContext('TriggerContext', { selectedTrigger: selectedTriggerStore, primarySchedule: primaryScheduleStore, - triggersCount + triggersCount, + simplifiedPoll }) async function loadTriggers() { @@ -876,7 +884,6 @@ await tick() select(module.id) await tick() - await tick() focusCopilot() let isFirstInLoop = false diff --git a/frontend/src/lib/components/FlowGraphViewer.svelte b/frontend/src/lib/components/FlowGraphViewer.svelte index 1ecef2943f..326e1275b6 100644 --- a/frontend/src/lib/components/FlowGraphViewer.svelte +++ b/frontend/src/lib/components/FlowGraphViewer.svelte @@ -7,6 +7,7 @@ import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' import { dfs } from './flows/dfs' + import { workspaceStore } from '$lib/stores' export let flow: { summary: string @@ -22,6 +23,7 @@ export let noGraph = false export let triggerNode = false export let stepDetail: FlowModule | string | undefined = undefined + export let workspace: string | undefined = $workspaceStore const dispatch = createEventDispatcher() @@ -37,6 +39,7 @@ path={flow?.path} {download} minHeight={400} + {workspace} modules={flow?.value?.modules} failureModule={flow?.value?.failure_module} preprocessorModule={flow?.value?.preprocessor_module} diff --git a/frontend/src/lib/components/FlowPlugConnect.svelte b/frontend/src/lib/components/FlowPlugConnect.svelte new file mode 100644 index 0000000000..2bd993b92a --- /dev/null +++ b/frontend/src/lib/components/FlowPlugConnect.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index e881dad539..2c9aa54461 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -29,6 +29,7 @@ import Alert from './common/alert/Alert.svelte' import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' + import { buildPrefix } from './graph/graphBuilder' const dispatch = createEventDispatcher() @@ -74,6 +75,10 @@ export let wideResults = false export let hideFlowResult = false + export let workspace: string | undefined = $workspaceStore + export let prefix: string | undefined = undefined + export let subflowParentsGlobalModuleStates: Writable>[] = [] + export let subflowParentsDurationStatuses: Writable>[] = [] let jobResults: any[] = flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? [] @@ -83,6 +88,7 @@ let localModuleStates: Writable> = writable({}) let localDurationStatuses: Writable> = writable({}) + let expandedSubflows: Record = {} export let job: Job | undefined = undefined @@ -94,6 +100,24 @@ // } // } + function updateModuleStates( + moduleState: Writable>, + key: string, + newValue: GraphModuleState, + keepType: boolean | undefined + ) { + moduleState.update((x) => { + if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { + newValue.type = x[key].type + } + x[key] = newValue + return x + }) + } + + function buildSubflowKey(key: string, prefix: string | undefined) { + return prefix ? 'subflow:' + prefix + key : key + } function setModuleState( key: string, value: Partial, @@ -102,15 +126,14 @@ ) { let newValue = { ...($localModuleStates[key] ?? {}), ...value } if (!deepEqual($localModuleStates[key], value) || force) { - ;[localModuleStates, ...globalModuleStates].forEach((s) => { - s.update((x) => { - if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { - newValue.type = x[key].type - } - x[key] = newValue - return x - }) - }) + ;[localModuleStates, ...globalModuleStates].forEach((s) => + updateModuleStates(s, key, newValue, keepType) + ) + if (prefix) { + subflowParentsGlobalModuleStates.forEach((s) => + updateModuleStates(s, buildSubflowKey(key, prefix), newValue, keepType) + ) + } } } @@ -124,6 +147,14 @@ return x }) }) + if (prefix) { + subflowParentsDurationStatuses.forEach((s) => { + s.update((x) => { + x[buildSubflowKey(key, prefix)].byJob[id] = value + return x + }) + }) + } } } @@ -131,15 +162,25 @@ if ($localDurationStatuses[modId] == undefined) { $localDurationStatuses[modId] = { byJob: {} } } - let prefixed = modId globalDurationStatuses.forEach((x) => x.update((x) => { - if (x[prefixed] == undefined) { - x[prefixed] = { byJob: {} } + if (x[modId] == undefined) { + x[modId] = { byJob: {} } } return x }) ) + if (prefix) { + subflowParentsDurationStatuses.forEach((x) => + x.update((x) => { + let key = buildSubflowKey(modId, prefix) + if (x[key] == undefined) { + x[key] = { byJob: {} } + } + return x + }) + ) + } } let innerModules: FlowStatusModule[] = [] @@ -368,7 +409,7 @@ if (globalRefreshes) { let modId = flowJobIds?.moduleId if (modId) { - globalRefreshes[modId] = async (loopJob) => { + globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => { setIteration(loopJob.index, loopJob.job, false, modId ?? '') refresh(true, loopJob) } @@ -631,6 +672,36 @@ }) } } + + function allModulesForTimeline( + modules: FlowModule[], + expandedSubflows: Record + ): string[] { + const ids = dfs(modules, (x) => x.id) + + function rec(ids: string[], prefix: string | undefined): string[] { + return ids.concat( + ids.flatMap((id) => { + let fms = expandedSubflows[id] + let oid = id.split(':').pop() + if (!oid) { + return [] + } + let nprefix = buildPrefix(prefix, oid) + return fms + ? rec( + dfs(fms, (x) => + x.id.startsWith('subflow:') ? x.id : buildSubflowKey(x.id, nprefix) + ), + nprefix + ) + : [] + }) + ) + } + + return rec(ids, undefined) + } {#if notAnonynmous} @@ -803,6 +874,9 @@ job={storedListJobs[j]} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={forloop_selected == loopJobId && selected == 'sequence' && render} reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20} {workspaceId} @@ -880,6 +954,9 @@ {childFlow} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={failedRetry == retry_selected && render} reducedPolling={false} {workspaceId} @@ -895,6 +972,17 @@ bind:refresh={recursiveRefresh[mod.job ?? '']} globalModuleStates={[]} globalDurationStatuses={[]} + prefix={buildPrefix(prefix, mod.id ?? '')} + subflowParentsGlobalModuleStates={[ + localModuleStates, + ...globalModuleStates, + ...subflowParentsGlobalModuleStates + ]} + subflowParentsDurationStatuses={[ + localDurationStatuses, + ...globalDurationStatuses, + ...subflowParentsDurationStatuses + ]} render={selected == 'sequence' && render} {workspaceId} jobId={mod.job} @@ -915,6 +1003,9 @@ globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} render={selected == 'sequence' && render} {workspaceId} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} jobId={mod.job} innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined} flowJobIds={mod.flow_jobs @@ -973,6 +1064,7 @@ minHeight={wrapperHeight} success={jobId != undefined && isSuccess(job?.['success'])} flowModuleStates={$localModuleStates} + bind:expandedSubflows on:select={(e) => { if (rightColumnSelect != 'node_definition') { rightColumnSelect = 'node_status' @@ -1004,10 +1096,13 @@ selectedForloopIndex: detail.index }) globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index }) + // console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes) }} modules={job.raw_flow?.modules ?? []} failureModule={job.raw_flow?.failure_module} preprocessorModule={job.raw_flow?.preprocessor_module} + allowSimplifiedPoll={false} + {workspace} />
x.id)} + flowModules={allModulesForTimeline( + job?.raw_flow?.modules ?? [], + expandedSubflows ?? {} + )} durationStatuses={localDurationStatuses} /> {:else if rightColumnSelect == 'node_status'} diff --git a/frontend/src/lib/components/FlowTimeline.svelte b/frontend/src/lib/components/FlowTimeline.svelte index 3f918c8c6b..4027eca9c3 100644 --- a/frontend/src/lib/components/FlowTimeline.svelte +++ b/frontend/src/lib/components/FlowTimeline.svelte @@ -178,7 +178,7 @@ {/if}
-
{k}
+
{k.startsWith('subflow:') ? k.substring(8) : k}
{#if min && total}
diff --git a/frontend/src/lib/components/InputTransformForm.svelte b/frontend/src/lib/components/InputTransformForm.svelte index 64530dd3ba..c0429a8284 100644 --- a/frontend/src/lib/components/InputTransformForm.svelte +++ b/frontend/src/lib/components/InputTransformForm.svelte @@ -1,3 +1,12 @@ + + {#if arg != undefined} -
-
+
+
{/if} - + {#if codeInjectionDetected && propertyType == 'static'} + + {:else} + + {/if}
- + />
{/if}
-
- {#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'} +
+ - {#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle} - {#if argName && schema?.properties?.[argName]?.description} -
-
{schema.properties[argName].description}
-
- {/if} -
- {#if arg} - +
+ {#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle} + {#if argName && schema?.properties?.[argName]?.description} +
+
{schema.properties[argName].description}
+
+ {/if} +
+ {#if arg} + { + focused = false + }} + bind:code={arg.value} + fontSize={14} + on:change={() => { + dispatch('change', { argName }) + }} + /> + {/if} +
+ {:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]} + { focused = false }} - bind:code={arg.value} - fontSize={14} + shouldDispatchChanges on:change={() => { dispatch('change', { argName }) }} + label={argName} + bind:editor={monaco} + bind:description={schema.properties[argName].description} + bind:value={arg.value} + type={schema.properties[argName].type} + oneOf={schema.properties[argName].oneOf} + required={schema.required?.includes(argName)} + bind:pattern={schema.properties[argName].pattern} + bind:valid={inputCheck} + defaultValue={schema.properties[argName].default} + bind:enum_={schema.properties[argName].enum} + bind:format={schema.properties[argName].format} + contentEncoding={schema.properties[argName].contentEncoding} + bind:itemsType={schema.properties[argName].items} + properties={schema.properties[argName].properties} + nestedRequired={schema.properties[argName].required} + displayHeader={false} + extra={argExtra} + {variableEditor} + {itemPicker} + bind:pickForField + showSchemaExplorer + nullable={schema.properties[argName].nullable} + bind:title={schema.properties[argName].title} + bind:placeholder={schema.properties[argName].placeholder} /> + {:else if arg.expr != undefined} +
+ { + dispatch('change', { argName }) + }} + {extraLib} + lang="javascript" + shouldBindKey={false} + on:focus={() => { + focused = true + focusProp(argName, 'insert', (path) => { + monaco?.insertAtCursor(path) + return false + }) + }} + on:change={() => { + dispatch('change', { argName }) + }} + on:blur={() => { + focused = false + }} + autoHeight + /> +
+ +
+ {:else} + Not recognized input type {argName} ({arg.expr}, {propertyType}) +
+
{/if}
- {:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]} - { - focused = false - }} - shouldDispatchChanges - on:change={() => { - dispatch('change', { argName }) - }} - label={argName} - bind:editor={monaco} - bind:description={schema.properties[argName].description} - bind:value={arg.value} - type={schema.properties[argName].type} - oneOf={schema.properties[argName].oneOf} - required={schema.required?.includes(argName)} - bind:pattern={schema.properties[argName].pattern} - bind:valid={inputCheck} - defaultValue={schema.properties[argName].default} - bind:enum_={schema.properties[argName].enum} - bind:format={schema.properties[argName].format} - contentEncoding={schema.properties[argName].contentEncoding} - bind:itemsType={schema.properties[argName].items} - properties={schema.properties[argName].properties} - nestedRequired={schema.properties[argName].required} - displayHeader={false} - extra={argExtra} - {variableEditor} - {itemPicker} - bind:pickForField - showSchemaExplorer - nullable={schema.properties[argName].nullable} - bind:title={schema.properties[argName].title} - bind:placeholder={schema.properties[argName].placeholder} - /> - {:else if arg.expr != undefined} -
- { - dispatch('change', { argName }) - }} - {extraLib} - lang="javascript" - shouldBindKey={false} - on:focus={() => { - focused = true - focusProp(argName, 'insert', (path) => { - monaco?.insertAtCursor(path) - return false - }) - }} - on:change={() => { - dispatch('change', { argName }) - }} - on:blur={() => { - focused = false - }} - autoHeight - /> -
- -
- {:else} - Not recognized input type {argName} ({arg.expr}, {propertyType}) -
-
- {/if} + + {#if $propPickerConfig?.propName == argName} +
+ + + +
+ {:else} +
+ {/if} +
{/if} diff --git a/frontend/src/lib/components/InputTransformSchemaForm.svelte b/frontend/src/lib/components/InputTransformSchemaForm.svelte index 488797c6db..12c85c05c6 100644 --- a/frontend/src/lib/components/InputTransformSchemaForm.svelte +++ b/frontend/src/lib/components/InputTransformSchemaForm.svelte @@ -67,24 +67,26 @@
{#if enableAi} - - Object.keys(schema.properties ?? {}).includes(argName) && - Object.keys(args ?? {}).includes(argName) && - ((args[argName].type === 'static' && !args[argName].value) || - (args[argName].type === 'javascript' && !args[argName].expr)) - ) - : []} - {schema} - /> +
+ + Object.keys(schema.properties ?? {}).includes(argName) && + Object.keys(args ?? {}).includes(argName) && + ((args[argName].type === 'static' && !args[argName].value) || + (args[argName].type === 'javascript' && !args[argName].expr)) + ) + : []} + {schema} + /> +
{/if} {#if keys.length > 0} {#each keys as argName (argName)} {#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)} -
+
{/if} @@ -965,12 +1014,26 @@
+
+ { + if (values[setting.key].smtp_disable_tls) { + values[setting.key].smtp_tls_implicit = false + } + }} + options={{ right: 'Disable TLS' }} + label="Disable TLS" + /> +
{/if}
{:else if setting.fieldType == 'object_store_config'} @@ -1033,7 +1096,8 @@ password: smtp['smtp_password'], port: smtp['smtp_port'], from: smtp['smtp_from'], - tls_implicit: smtp['smtp_tls_implicit'] + tls_implicit: smtp['smtp_tls_implicit'], + disable_tls: smtp['smtp_disable_tls'] } } }) diff --git a/frontend/src/lib/components/LightweightArgInput.svelte b/frontend/src/lib/components/LightweightArgInput.svelte index bc15a41926..f9c4a04ed0 100644 --- a/frontend/src/lib/components/LightweightArgInput.svelte +++ b/frontend/src/lib/components/LightweightArgInput.svelte @@ -61,6 +61,18 @@ export let render = true export let title: string | undefined = undefined export let placeholder: string | undefined = undefined + export let appPath: string | undefined = undefined + export let computeS3ForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined + export let workspace: string | undefined = undefined let oneOfSelected: string | undefined = undefined async function updateOneOfSelected(oneOf: SchemaProperty[] | undefined) { @@ -428,6 +440,9 @@ .toLowerCase() == 's3object'}
{ diff --git a/frontend/src/lib/components/LightweightResourcePicker.svelte b/frontend/src/lib/components/LightweightResourcePicker.svelte index 21954ac1ca..65db78cbe4 100644 --- a/frontend/src/lib/components/LightweightResourcePicker.svelte +++ b/frontend/src/lib/components/LightweightResourcePicker.svelte @@ -202,6 +202,7 @@ path: valueSelect.label }) await loadResources(resourceType) + value = undefined valueSelect = undefined } }} @@ -216,6 +217,7 @@ btnClasses="w-8 px-0.5 py-1.5" size="sm" on:click={() => { + refreshCount += 1 open = true drawer?.openDrawer?.() }} diff --git a/frontend/src/lib/components/LightweightSchemaForm.svelte b/frontend/src/lib/components/LightweightSchemaForm.svelte index d4da597066..55b1373b4d 100644 --- a/frontend/src/lib/components/LightweightSchemaForm.svelte +++ b/frontend/src/lib/components/LightweightSchemaForm.svelte @@ -16,6 +16,18 @@ export let defaultValues: Record = {} export let dynamicEnums: Record = {} export let disabled: boolean = false + export let appPath: string | undefined = undefined + export let computeS3ForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined + export let workspace: string | undefined = undefined let inputCheck: { [id: string]: boolean } = {} let errors: { [id: string]: string } = {} @@ -104,6 +116,9 @@ {displayType} {css} disabled={disabled || schema.properties[argName].disabled} + {appPath} + {computeS3ForceViewerPolicies} + {workspace} /> {/if} {/each} diff --git a/frontend/src/lib/components/LogSnippetViewer.svelte b/frontend/src/lib/components/LogSnippetViewer.svelte new file mode 100644 index 0000000000..693522ed33 --- /dev/null +++ b/frontend/src/lib/components/LogSnippetViewer.svelte @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/lib/components/LogViewer.svelte b/frontend/src/lib/components/LogViewer.svelte index de78eefc52..d04b7f9c56 100644 --- a/frontend/src/lib/components/LogViewer.svelte +++ b/frontend/src/lib/components/LogViewer.svelte @@ -245,7 +245,7 @@ : 'top-2'} left-36">mem peak: {(mem / 1024).toPrecision(4)}MB {/if} -
{#if content}{@const len =
 					(content?.length ?? 0) +
 					(loadedFromObjectStore?.length ?? 0)}{#if downloadStartUrl}
-			
-			{#if initialPrimarySchedule != undefined && initialPrimarySchedule != false && !newItem}
+			
+ +
+ {#if $initialPrimarySchedule != undefined && $initialPrimarySchedule != false && !newItem} @@ -274,12 +208,12 @@ {/if}