diff --git a/CHANGELOG.md b/CHANGELOG.md index adaa14ec7a..c4dda9f297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # Changelog +## [1.414.1](https://github.com/windmill-labs/windmill/compare/v1.414.0...v1.414.1) (2024-10-29) + + +### Bug Fixes + +* **apps:** enable text selection on aggrid tables by default ([b0b9180](https://github.com/windmill-labs/windmill/commit/b0b9180fb907c92b95a48ff286eb1dae59bb4981)) +* **apps:** public apps can take full height ([703db7d](https://github.com/windmill-labs/windmill/commit/703db7d4412795b4323a2eefcc39ae3cf43bc748)) +* **bun:** handle bun lockfile created with windows ([#4602](https://github.com/windmill-labs/windmill/issues/4602)) ([dcf5e2f](https://github.com/windmill-labs/windmill/commit/dcf5e2f03f977e241f6785530dad61a70c5bdd79)) +* **frontend:** make script and schema scrollable on script detail page ([6e222b3](https://github.com/windmill-labs/windmill/commit/6e222b3b1a419e5543fdf35420a7413699688b41)) +* **frontend:** new approval steps default to timeout 1800 ([b86de62](https://github.com/windmill-labs/windmill/commit/b86de6280e03e014e8ddf85b2b5f8fd030d0467a)) + +## [1.414.0](https://github.com/windmill-labs/windmill/compare/v1.413.2...v1.414.0) (2024-10-29) + +* Issue with previous release, re-releasing + +## [1.413.2](https://github.com/windmill-labs/windmill/compare/v1.413.1...v1.413.2) (2024-10-29) + + +### Bug Fixes + +* **backend:** in flows, workspace scripts should use their set tags instead of the default one ([5b7c6d7](https://github.com/windmill-labs/windmill/commit/5b7c6d7d62dcfd09fec374e781bdf5c5bafe4a9d)) +* **cli:** fix wmill instance pull --instance ([3c62f5e](https://github.com/windmill-labs/windmill/commit/3c62f5ea83d1da8bd3705468969d56e2fe680751)) +* **frontend:** fix script and flow renaming ([d743e00](https://github.com/windmill-labs/windmill/commit/d743e0056353a4fca445a7089e3afc1fd4e8c219)) + +## [1.413.1](https://github.com/windmill-labs/windmill/compare/v1.413.0...v1.413.1) (2024-10-28) + + +### Bug Fixes + +* **cli:** fix wmill instance push --base-url and --instance ([8298710](https://github.com/windmill-labs/windmill/commit/82987105a6fd6ec272c170fb094453a0267143be)) + +## [1.413.0](https://github.com/windmill-labs/windmill/compare/v1.412.0...v1.413.0) (2024-10-28) + + +### Features + +* autoscaling v0 ([#4593](https://github.com/windmill-labs/windmill/issues/4593)) ([fe7d044](https://github.com/windmill-labs/windmill/commit/fe7d044a66e8ec223a337cb704d3e58942dd1502)) + + +### Bug Fixes + +* add run immediately popover to run again ([e54d253](https://github.com/windmill-labs/windmill/commit/e54d25368541dc6109a0f99022a120d28455f9bd)) +* **docs:** smtp setup documentation link ([#4590](https://github.com/windmill-labs/windmill/issues/4590)) ([bac3205](https://github.com/windmill-labs/windmill/commit/bac32057259d893140d649c9dfec2ca75e395ad4)) + +## [1.412.0](https://github.com/windmill-labs/windmill/compare/v1.411.1...v1.412.0) (2024-10-25) + + +### Features + +* add Spotify oauth provider ([#4581](https://github.com/windmill-labs/windmill/issues/4581)) ([a46aa64](https://github.com/windmill-labs/windmill/commit/a46aa644b096e71e75b59507224ed92f7d2f99ba)) + + +### Bug Fixes + +* **app builder:** date input default value improvements ([9f43d5d](https://github.com/windmill-labs/windmill/commit/9f43d5dcd92ddcd0c0baeaa5271779520af652f4)) +* **bash:** correctly propagate sigterm for cancelled bash scripts ([134cfdb](https://github.com/windmill-labs/windmill/commit/134cfdb30eb8d29c2ecb1b78b8fabae2b6e10700)) +* do not update created_at of scripts on lockfile generation ([d1a28eb](https://github.com/windmill-labs/windmill/commit/d1a28eb7cac5f465e2b07f870a507b0cc5cc722a)) +* initialize empty smtp settings correctly ([84e0524](https://github.com/windmill-labs/windmill/commit/84e05249505c3d2f7eb7a4917f6bca503df81017)) + ## [1.411.1](https://github.com/windmill-labs/windmill/compare/v1.411.0...v1.411.1) (2024-10-22) diff --git a/Dockerfile b/Dockerfile index c3b050df88..a39a54966c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -163,7 +163,7 @@ ENV GO_PATH=/usr/local/go/bin/go 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 RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - -RUN apt-get -y update && apt-get install -y curl nodejs awscli && apt-get clean \ +RUN apt-get -y update && apt-get install -y curl procps nodejs awscli && apt-get clean \ && rm -rf /var/lib/apt/lists/* # go build is slower the first time it is ran, so we prewarm it in the build diff --git a/backend/.sqlx/query-02b516dac764662194db1bc33e365c01f40bae70af3683f1f09748f6020f0d49.json b/backend/.sqlx/query-02b516dac764662194db1bc33e365c01f40bae70af3683f1f09748f6020f0d49.json deleted file mode 100644 index 99f3606bcd..0000000000 --- a/backend/.sqlx/query-02b516dac764662194db1bc33e365c01f40bae70af3683f1f09748f6020f0d49.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT tag, count(*) as count FROM queue WHERE\n scheduled_for <= now() - ('3 seconds')::interval AND running = false\n GROUP BY tag", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - null - ] - }, - "hash": "02b516dac764662194db1bc33e365c01f40bae70af3683f1f09748f6020f0d49" -} diff --git a/backend/.sqlx/query-19f0abd79372698f378cb6deea3ee6d098a2758d16ede000809bd9a09660b604.json b/backend/.sqlx/query-19f0abd79372698f378cb6deea3ee6d098a2758d16ede000809bd9a09660b604.json deleted file mode 100644 index 8796b86902..0000000000 --- a/backend/.sqlx/query-19f0abd79372698f378cb6deea3ee6d098a2758d16ede000809bd9a09660b604.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT created_at FROM metrics WHERE id = 'telemetry' ORDER BY created_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "19f0abd79372698f378cb6deea3ee6d098a2758d16ede000809bd9a09660b604" -} diff --git a/backend/.sqlx/query-c2060e8cacef6c3b5ce51ed203a2dbafc18d66f2924d1fe518c6728997647db2.json b/backend/.sqlx/query-1a4d291c2f239f7b50c116594cebb031862e1a18ad9204e02a0194817db26d6a.json similarity index 75% rename from backend/.sqlx/query-c2060e8cacef6c3b5ce51ed203a2dbafc18d66f2924d1fe518c6728997647db2.json rename to backend/.sqlx/query-1a4d291c2f239f7b50c116594cebb031862e1a18ad9204e02a0194817db26d6a.json index 6a08bbeab3..b8c5b89128 100644 --- a/backend/.sqlx/query-c2060e8cacef6c3b5ce51ed203a2dbafc18d66f2924d1fe518c6728997647db2.json +++ b/backend/.sqlx/query-1a4d291c2f239f7b50c116594cebb031862e1a18ad9204e02a0194817db26d6a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT email, login_type::text, verified, super_admin, name, company, username from password ORDER BY super_admin DESC, email LIMIT $1 OFFSET $2", + "query": "SELECT email, login_type::text, verified, super_admin, name, company, username, NULL::bool as operator_only FROM password ORDER BY super_admin DESC, email LIMIT $1 OFFSET $2", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "ordinal": 6, "name": "username", "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "operator_only", + "type_info": "Bool" } ], "parameters": { @@ -52,8 +57,9 @@ false, true, true, - true + true, + null ] }, - "hash": "c2060e8cacef6c3b5ce51ed203a2dbafc18d66f2924d1fe518c6728997647db2" + "hash": "1a4d291c2f239f7b50c116594cebb031862e1a18ad9204e02a0194817db26d6a" } diff --git a/backend/.sqlx/query-1f5f0858909eb5bac63c4e3b1add95226bd94ca3facb92a620ffa59dacad6705.json b/backend/.sqlx/query-1f5f0858909eb5bac63c4e3b1add95226bd94ca3facb92a620ffa59dacad6705.json new file mode 100644 index 0000000000..0adff2fe2c --- /dev/null +++ b/backend/.sqlx/query-1f5f0858909eb5bac63c4e3b1add95226bd94ca3facb92a620ffa59dacad6705.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO autoscaling_event (worker_group, event_type, desired_workers, reason) VALUES ($1, $2, $3, $4)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + { + "Custom": { + "name": "autoscaling_event_type", + "kind": { + "Enum": [ + "full_scaleout", + "scalein", + "scaleout" + ] + } + } + }, + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "1f5f0858909eb5bac63c4e3b1add95226bd94ca3facb92a620ffa59dacad6705" +} diff --git a/backend/.sqlx/query-8813665f8adfcab0daefbac2cc6b50e427dfd1c12d12895451affd307dc59c37.json b/backend/.sqlx/query-2c14d3a88193f16ad3b8cd590749cb5537995f2499f6cb8f0f316fb62902d542.json similarity index 77% rename from backend/.sqlx/query-8813665f8adfcab0daefbac2cc6b50e427dfd1c12d12895451affd307dc59c37.json rename to backend/.sqlx/query-2c14d3a88193f16ad3b8cd590749cb5537995f2499f6cb8f0f316fb62902d542.json index ce8adcd5b7..93017f5aec 100644 --- a/backend/.sqlx/query-8813665f8adfcab0daefbac2cc6b50e427dfd1c12d12895451affd307dc59c37.json +++ b/backend/.sqlx/query-2c14d3a88193f16ad3b8cd590749cb5537995f2499f6cb8f0f316fb62902d542.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT email, login_type::TEXT, super_admin, verified, name, company, username FROM password WHERE email = $1", + "query": "SELECT email, login_type::TEXT, super_admin, verified, name, company, username, NULL::bool as operator_only FROM password WHERE email = $1", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "ordinal": 6, "name": "username", "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "operator_only", + "type_info": "Bool" } ], "parameters": { @@ -51,8 +56,9 @@ false, true, true, - true + true, + null ] }, - "hash": "8813665f8adfcab0daefbac2cc6b50e427dfd1c12d12895451affd307dc59c37" + "hash": "2c14d3a88193f16ad3b8cd590749cb5537995f2499f6cb8f0f316fb62902d542" } diff --git a/backend/.sqlx/query-2eec077cc9e27d7ccd160cbaac118c321c422705f79e69550bb60f377083bcef.json b/backend/.sqlx/query-2eec077cc9e27d7ccd160cbaac118c321c422705f79e69550bb60f377083bcef.json new file mode 100644 index 0000000000..f3eecb9006 --- /dev/null +++ b/backend/.sqlx/query-2eec077cc9e27d7ccd160cbaac118c321c422705f79e69550bb60f377083bcef.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, name, company, username\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC\n LIMIT $1 OFFSET $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "operator_only", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "login_type", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "verified", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "super_admin", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "company", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "username", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + null, + null, + false, + false, + true, + true, + true + ] + }, + "hash": "2eec077cc9e27d7ccd160cbaac118c321c422705f79e69550bb60f377083bcef" +} diff --git a/backend/.sqlx/query-4eca060026a0cb19c5794cd56ace89fc04765191f251945d14bbe78718714f6e.json b/backend/.sqlx/query-4eca060026a0cb19c5794cd56ace89fc04765191f251945d14bbe78718714f6e.json new file mode 100644 index 0000000000..3b812a53db --- /dev/null +++ b/backend/.sqlx/query-4eca060026a0cb19c5794cd56ace89fc04765191f251945d14bbe78718714f6e.json @@ -0,0 +1,39 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT event_type::AUTOSCALING_EVENT_TYPE AS \"event_type: _\", EXTRACT(EPOCH FROM (NOW() - applied_at))::int as seconds_ago FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "event_type: _", + "type_info": { + "Custom": { + "name": "autoscaling_event_type", + "kind": { + "Enum": [ + "full_scaleout", + "scalein", + "scaleout" + ] + } + } + } + }, + { + "ordinal": 1, + "name": "seconds_ago", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + null + ] + }, + "hash": "4eca060026a0cb19c5794cd56ace89fc04765191f251945d14bbe78718714f6e" +} diff --git a/backend/.sqlx/query-6776dc50f184188756ad7fe263b0304333536768527525a43bdd45aedffa3c4f.json b/backend/.sqlx/query-6776dc50f184188756ad7fe263b0304333536768527525a43bdd45aedffa3c4f.json deleted file mode 100644 index 4187b628b7..0000000000 --- a/backend/.sqlx/query-6776dc50f184188756ad7fe263b0304333536768527525a43bdd45aedffa3c4f.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT pg_try_advisory_xact_lock($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "pg_try_advisory_xact_lock", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - null - ] - }, - "hash": "6776dc50f184188756ad7fe263b0304333536768527525a43bdd45aedffa3c4f" -} diff --git a/backend/.sqlx/query-6d134b137ae81534e145fc5b6474cf963ee26a3ad3a0a3d8dc064cb14c8fd9a6.json b/backend/.sqlx/query-6d134b137ae81534e145fc5b6474cf963ee26a3ad3a0a3d8dc064cb14c8fd9a6.json new file mode 100644 index 0000000000..03351cdeab --- /dev/null +++ b/backend/.sqlx/query-6d134b137ae81534e145fc5b6474cf963ee26a3ad3a0a3d8dc064cb14c8fd9a6.json @@ -0,0 +1,52 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT 5", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "worker_group", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "event_type", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "desired_workers", + "type_info": "Int4" + }, + { + "ordinal": 4, + "name": "reason", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "applied_at", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + null, + false, + true, + false + ] + }, + "hash": "6d134b137ae81534e145fc5b6474cf963ee26a3ad3a0a3d8dc064cb14c8fd9a6" +} diff --git a/backend/.sqlx/query-9ed77e78e6295c62745ac3ac3b7e5f544f654d7cddc13acb5d3f4fdc12a8875f.json b/backend/.sqlx/query-9ed77e78e6295c62745ac3ac3b7e5f544f654d7cddc13acb5d3f4fdc12a8875f.json deleted file mode 100644 index 9c7af05c42..0000000000 --- a/backend/.sqlx/query-9ed77e78e6295c62745ac3ac3b7e5f544f654d7cddc13acb5d3f4fdc12a8875f.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT created_at FROM metrics WHERE id = 'license_key_renewal' ORDER BY created_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "9ed77e78e6295c62745ac3ac3b7e5f544f654d7cddc13acb5d3f4fdc12a8875f" -} diff --git a/backend/.sqlx/query-ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61.json b/backend/.sqlx/query-d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8.json similarity index 51% rename from backend/.sqlx/query-ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61.json rename to backend/.sqlx/query-d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8.json index 2ed5e047db..e35c4e2730 100644 --- a/backend/.sqlx/query-ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61.json +++ b/backend/.sqlx/query-d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE script SET lock = $1, created_at = now() WHERE hash = $2 AND workspace_id = $3", + "query": "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61" + "hash": "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8" } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 6e3d854a2a..ba671e1b77 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -132,36 +132,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -441,7 +441,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -571,7 +571,7 @@ dependencies = [ "bytes", "http 1.1.0", "rand 0.8.5", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde-aux", "serde_json", @@ -588,7 +588,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -636,7 +636,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -678,7 +678,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -732,9 +732,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.8" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6" +checksum = "2d6448cfb224dd6a9b9ac734f58622dd0d4751f3589f3b777345745f46b2eb14" dependencies = [ "aws-credential-types", "aws-runtime", @@ -799,9 +799,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc2faec3205d496c7e57eff685dd944203df7ce16a4116d0281c44021788a7b" +checksum = "a8776850becacbd3a82a4737a9375ddb5c6832a51379f24443a98e61513f852c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -821,9 +821,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.47.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c93c241f52bc5e0476e259c953234dab7e2a35ee207ee202e86c0095ec4951dc" +checksum = "0007b5b8004547133319b6c4e87193eee2a0bcb3e4c18c75d09febe9dab7b383" dependencies = [ "aws-credential-types", "aws-runtime", @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b259429be94a3459fa1b00c5684faee118d74f9577cc50aebadc36e507c63b5f" +checksum = "9fffaa356e7f1c725908b75136d53207fa714e348f365671df14e95a60530ad3" dependencies = [ "aws-credential-types", "aws-runtime", @@ -866,9 +866,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -939,9 +939,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a065c0fe6fdbdf9f11817eb68582b2ab4aff9e9c39e986ae48f7ec576c6322db" +checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +checksum = "07c9cdc179e6afbf5d391ab08c85eac817b51c87e1892a5edb5f7bbdc64314b4" dependencies = [ "base64-simd 0.8.0", "bytes", @@ -1179,9 +1179,9 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" dependencies = [ "autocfg", "libm", @@ -1212,13 +1212,13 @@ dependencies = [ "lazy_static", "lazycell", "log", - "prettyplease 0.2.24", + "prettyplease 0.2.25", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.82", + "syn 2.0.85", "which 4.4.2", ] @@ -1237,7 +1237,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -1381,7 +1381,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "syn_derive", ] @@ -1484,7 +1484,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -1744,7 +1744,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -1764,9 +1764,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -2124,7 +2124,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -2172,7 +2172,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -2194,7 +2194,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -2715,7 +2715,7 @@ dependencies = [ "quote", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.82", + "syn 2.0.85", "thiserror", ] @@ -2777,7 +2777,7 @@ checksum = "22a1abc6bd8af41aa2496ceceef94f4277092c781a9495c437327a17659553a2" dependencies = [ "deno_core", "deno_native_certs", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pemfile 2.2.0", "rustls-tokio-stream", "rustls-webpki 0.102.8", @@ -2907,7 +2907,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3004,7 +3004,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3160,7 +3160,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3180,7 +3180,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3395,7 +3395,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3427,7 +3427,7 @@ checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3439,7 +3439,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3451,7 +3451,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3575,7 +3575,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -3828,7 +3828,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -4265,7 +4265,7 @@ dependencies = [ "http 1.1.0", "hyper 1.5.0", "hyper-util", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -4463,7 +4463,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -4700,9 +4700,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -4889,7 +4889,7 @@ dependencies = [ "base64 0.22.1", "gethostname", "mail-builder", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pki-types", "smtp-proto", "tokio", @@ -5113,7 +5113,7 @@ checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -5152,7 +5152,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "termcolor", "thiserror", ] @@ -5445,7 +5445,7 @@ dependencies = [ "percent-encoding", "quick-xml 0.36.2", "rand 0.8.5", - "reqwest 0.12.8", + "reqwest 0.12.9", "ring 0.17.8", "serde", "serde_json", @@ -5562,7 +5562,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -5839,7 +5839,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -5865,29 +5865,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -6057,12 +6057,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -6115,7 +6115,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -6127,7 +6127,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -6188,7 +6188,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "syn 2.0.82", + "syn 2.0.85", "thiserror", "typify", "unicode-ident", @@ -6208,7 +6208,7 @@ dependencies = [ "serde_json", "serde_tokenstream", "serde_yaml", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -6251,7 +6251,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -6365,7 +6365,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.15", + "rustls 0.23.16", "socket2 0.5.7", "thiserror", "tokio", @@ -6382,7 +6382,7 @@ dependencies = [ "rand 0.8.5", "ring 0.17.8", "rustc-hash 2.0.0", - "rustls 0.23.15", + "rustls 0.23.16", "slab", "thiserror", "tinyvec", @@ -6391,10 +6391,11 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "e346e016eacfff12233c243718197ca12f148c84e1e84268a896699b41c71780" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2 0.5.7", @@ -6603,9 +6604,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -6724,9 +6725,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "async-compression 0.4.17", "base64 0.22.1", @@ -6751,7 +6752,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-native-certs 0.8.0", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -6934,7 +6935,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.82", + "syn 2.0.85", "walkdir", ] @@ -7026,9 +7027,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -7051,9 +7052,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "log", "once_cell", @@ -7133,7 +7134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33" dependencies = [ "futures", - "rustls 0.23.15", + "rustls 0.23.16", "socket2 0.5.7", "tokio", ] @@ -7314,7 +7315,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -7414,9 +7415,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.212" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd4055b7e3937a5c2595e974f5bf1715a23919a595a04b5ad959bdbbb61ab04" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -7455,13 +7456,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.212" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726adf8349784fb68a42e6466f49362ae039d9c5333cc6eb131f4d6f94bb9126" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -7472,7 +7473,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -7547,7 +7548,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -7602,7 +7603,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -7971,7 +7972,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8016,7 +8017,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pemfile 2.2.0", "serde", "serde_json", @@ -8042,7 +8043,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8065,7 +8066,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.82", + "syn 2.0.85", "tempfile", "tokio", "url", @@ -8217,7 +8218,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8271,7 +8272,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8284,7 +8285,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8392,7 +8393,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8441,7 +8442,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8526,7 +8527,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8633,7 +8634,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8644,7 +8645,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8667,7 +8668,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8683,9 +8684,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -8701,7 +8702,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8727,7 +8728,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -8992,7 +8993,7 @@ checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -9217,7 +9218,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -9272,7 +9273,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pki-types", "tokio", ] @@ -9544,7 +9545,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -9607,7 +9608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea6023f9fe4b69267ccd3ed7d203d931c43c5f82dbaa0f07202bc17193a5f43" dependencies = [ "loki-api", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "snap", @@ -9781,7 +9782,7 @@ dependencies = [ "regress", "schemars", "serde_json", - "syn 2.0.82", + "syn 2.0.85", "thiserror", "unicode-ident", ] @@ -9798,7 +9799,7 @@ dependencies = [ "serde", "serde_json", "serde_tokenstream", - "syn 2.0.82", + "syn 2.0.85", "typify-impl", ] @@ -10007,7 +10008,7 @@ dependencies = [ "log", "native-tls", "once_cell", - "rustls 0.23.15", + "rustls 0.23.16", "rustls-pki-types", "serde", "serde_json", @@ -10107,9 +10108,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "vcpkg" @@ -10193,7 +10194,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "wasm-bindgen-shared", ] @@ -10227,7 +10228,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10260,7 +10261,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -10394,7 +10395,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "axum", @@ -10411,7 +10412,7 @@ dependencies = [ "prometheus", "quote", "rand 0.8.5", - "reqwest 0.12.8", + "reqwest 0.12.9", "rsmq_async", "serde", "serde_json", @@ -10426,6 +10427,7 @@ dependencies = [ "uuid 1.11.0", "windmill-api", "windmill-api-client", + "windmill-autoscaling", "windmill-common", "windmill-git-sync", "windmill-indexer", @@ -10435,7 +10437,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "argon2", @@ -10479,7 +10481,7 @@ dependencies = [ "quick_cache", "rand 0.8.5", "regex", - "reqwest 0.12.8", + "reqwest 0.12.9", "rsa 0.7.2", "rsmq_async", "rust-embed", @@ -10520,7 +10522,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.411.1" +version = "1.414.1" dependencies = [ "base64 0.21.7", "chrono", @@ -10538,7 +10540,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.411.1" +version = "1.414.1" dependencies = [ "chrono", "serde", @@ -10549,9 +10551,24 @@ dependencies = [ "windmill-common", ] +[[package]] +name = "windmill-autoscaling" +version = "1.414.1" +dependencies = [ + "anyhow", + "rsmq_async", + "serde", + "serde_json", + "sqlx", + "tracing", + "uuid 1.11.0", + "windmill-common", + "windmill-queue", +] + [[package]] name = "windmill-common" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "async-stream", @@ -10578,7 +10595,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "regex", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "sha2 0.10.8", @@ -10597,7 +10614,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.411.1" +version = "1.414.1" dependencies = [ "regex", "rsmq_async", @@ -10612,7 +10629,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "bytes", @@ -10634,19 +10651,19 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.411.1" +version = "1.414.1" dependencies = [ "itertools 0.13.0", "lazy_static", "proc-macro2", "quote", "regex", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] name = "windmill-parser" -version = "1.411.1" +version = "1.414.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -10655,7 +10672,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "lazy_static", @@ -10667,7 +10684,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "gosyn", @@ -10679,7 +10696,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "lazy_static", @@ -10691,7 +10708,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10702,7 +10719,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10713,7 +10730,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "async-recursion", @@ -10731,7 +10748,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10741,14 +10758,14 @@ dependencies = [ "quote", "regex", "serde_json", - "syn 2.0.82", + "syn 2.0.85", "toml 0.7.8", "windmill-parser", ] [[package]] name = "windmill-parser-sql" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "lazy_static", @@ -10760,7 +10777,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "lazy_static", @@ -10778,7 +10795,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10799,7 +10816,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "serde_json", @@ -10809,7 +10826,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "async-recursion", @@ -10825,7 +10842,7 @@ dependencies = [ "lazy_static", "prometheus", "regex", - "reqwest 0.12.8", + "reqwest 0.12.9", "rsmq_async", "serde", "serde_json", @@ -10842,7 +10859,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.411.1" +version = "1.414.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -10852,7 +10869,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.411.1" +version = "1.414.1" dependencies = [ "anyhow", "async-recursion", @@ -10892,7 +10909,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "regex", - "reqwest 0.12.8", + "reqwest 0.12.9", "rsmq_async", "rust_decimal", "serde", @@ -11218,7 +11235,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "synstructure", ] @@ -11240,7 +11257,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", ] [[package]] @@ -11260,7 +11277,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.85", "synstructure", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 11a097d2b7..cc23b4af6e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.411.1" +version = "1.414.1" authors.workspace = true edition.workspace = true @@ -13,6 +13,7 @@ members = [ "./windmill-common", "./windmill-audit", "./windmill-git-sync", + "./windmill-autoscaling", "./windmill-indexer", "./windmill-macros", "./parsers/windmill-parser", @@ -28,7 +29,7 @@ members = [ ] [workspace.package] -version = "1.411.1" +version = "1.414.1" authors = ["Ruben Fiszel "] edition = "2021" @@ -45,7 +46,7 @@ lto = "thin" [features] default = [] -enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-indexer/enterprise"] +enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-indexer/enterprise"] enterprise_saml = ["windmill-api/enterprise_saml"] stripe = ["windmill-api/stripe"] benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark", "windmill-common/benchmark"] @@ -72,6 +73,7 @@ windmill-git-sync.workspace = true windmill-api = { workspace = true, default-features = false } windmill-worker.workspace = true windmill-indexer = { workspace = true, optional = true } +windmill-autoscaling = { workspace = true, optional = true } futures.workspace = true tracing.workspace = true sqlx.workspace = true @@ -116,6 +118,7 @@ windmill-worker = { path = "./windmill-worker" } windmill-common = { path = "./windmill-common", default-features = false } windmill-audit = { path = "./windmill-audit" } windmill-git-sync = { path = "./windmill-git-sync" } +windmill-autoscaling = { path = "./windmill-autoscaling" } windmill-indexer = {path = "./windmill-indexer"} windmill-macros = {path = "./windmill-macros"} windmill-parser = { path = "./parsers/windmill-parser" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 0b60c124b1..2c2cd13f33 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -d61c163e0a311ecd86d1398aff883eaea8d0b09a +f667fcebdb5a2d1c22c1928d6a316ba57bb43d72 \ No newline at end of file diff --git a/backend/migrations/20241024125924_autoscaling.down.sql b/backend/migrations/20241024125924_autoscaling.down.sql new file mode 100644 index 0000000000..8f536a7553 --- /dev/null +++ b/backend/migrations/20241024125924_autoscaling.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +DROP TABLE autoscaling_event; +DROP TYPE autoscaling_event_type; diff --git a/backend/migrations/20241024125924_autoscaling.up.sql b/backend/migrations/20241024125924_autoscaling.up.sql new file mode 100644 index 0000000000..fa3b161d56 --- /dev/null +++ b/backend/migrations/20241024125924_autoscaling.up.sql @@ -0,0 +1,13 @@ +-- Add up migration script here +CREATE TYPE AUTOSCALING_EVENT_TYPE AS ENUM ('full_scaleout', 'scalein', 'scaleout'); + +CREATE TABLE autoscaling_event ( + id SERIAL PRIMARY KEY, + worker_group TEXT NOT NULL, + event_type AUTOSCALING_EVENT_TYPE NOT NULL, + desired_workers INTEGER NOT NULL, + applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + reason TEXT +); + +CREATE INDEX autoscaling_event_worker_group_idx ON autoscaling_event (worker_group, applied_at); \ No newline at end of file diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json index 034aece2f4..994d9999fd 100644 --- a/backend/oauth_connect.json +++ b/backend/oauth_connect.json @@ -152,5 +152,24 @@ "vismanet_erp_interactive_api:read", "vismanet_erp_interactive_api:update" ] + }, + "spotify": { + "auth_url": "https://accounts.spotify.com/authorize", + "token_url": "https://accounts.spotify.com/api/token", + "scopes": [ + "user-read-playback-state", + "user-modify-playback-state", + "user-read-currently-playing", + "playlist-read-private", + "playlist-read-collaborative", + "playlist-modify-private", + "playlist-modify-public", + "user-follow-read", + "user-read-playback-position", + "user-read-recently-played", + "user-top-read", + "user-library-modify", + "user-library-read" + ] } } \ No newline at end of file diff --git a/backend/parsers/windmill-parser-yaml/src/lib.rs b/backend/parsers/windmill-parser-yaml/src/lib.rs index f008f2f34a..8c29c97b7e 100644 --- a/backend/parsers/windmill-parser-yaml/src/lib.rs +++ b/backend/parsers/windmill-parser-yaml/src/lib.rs @@ -188,9 +188,15 @@ pub struct AnsiblePlaybookOptions { pub force_handlers: Option<()>, } +#[derive(Debug, Clone)] +pub enum ResourceOrVariablePath { + Resource(String), + Variable(String), +} + #[derive(Debug, Clone)] pub struct FileResource { - pub resource_path: String, + pub resource_path: ResourceOrVariablePath, pub target_path: String, } @@ -309,7 +315,7 @@ pub fn parse_ansible_reqs( } } } - Yaml::String(key) if key == "file_resources" => { + Yaml::String(key) if key == "files" || key == "file_resources" => { if let Yaml::Array(file_resources) = value { let resources: anyhow::Result> = file_resources.iter().map(parse_file_resource).collect(); @@ -440,7 +446,24 @@ fn parse_file_resource(yaml: &Yaml) -> anyhow::Result { "No `target` provided for file resource {}. Please input a target relative path for the ansible playbook to see this file.", resource_path ))?; - return Ok(FileResource { resource_path: resource_path.clone(), target_path }); + return Ok(FileResource { + resource_path: ResourceOrVariablePath::Resource(resource_path.clone()), + target_path, + }); + } + if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("variable".to_string())) { + let target_path = f + .get(&Yaml::String("target".to_string())) + .and_then(|x| x.as_str()) + .map(|x| x.to_string()) + .ok_or(anyhow!( + "No `target` provided for file resource {}. Please input a target relative path for the ansible playbook to see this file.", + resource_path + ))?; + return Ok(FileResource { + resource_path: ResourceOrVariablePath::Variable(resource_path.clone()), + target_path, + }); } return Err(anyhow!( "File resource should have a `resource` field, linking to a text file resource" diff --git a/backend/src/ee.rs b/backend/src/ee.rs index ef944b984b..91816cd1ba 100644 --- a/backend/src/ee.rs +++ b/backend/src/ee.rs @@ -1,16 +1,8 @@ -use anyhow::anyhow; -#[cfg(feature = "enterprise")] -use windmill_common::error::{Error, Result}; - -pub async fn set_license_key(_license_key: String) -> anyhow::Result<()> { +pub async fn set_license_key(_license_key: String) -> () { // Implementation is not open source - Err(anyhow!("License cannot be set in Windmill CE")) } #[cfg(feature = "enterprise")] -pub async fn verify_license_key() -> Result<()> { +pub async fn verify_license_key() -> () { // Implementation is not open source - Err(Error::InternalErr( - "License always invalid in Windmill CE".to_string(), - )) } diff --git a/backend/src/main.rs b/backend/src/main.rs index b84de925ef..20bd9fa00f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -27,7 +27,7 @@ use uuid::Uuid; use windmill_api::HTTP_CLIENT; #[cfg(feature = "enterprise")] -use windmill_common::ee::schedule_key_renewal; +use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID}; use windmill_common::{ global_settings::{ @@ -307,7 +307,8 @@ async fn windmill_main() -> anyhow::Result<()> { Mode::Standalone }); - let num_workers = if mode == Mode::Server || mode == Mode::Indexer { + #[allow(unused_mut)] + let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer { 0 } else { std::env::var("NUM_WORKERS") @@ -422,6 +423,50 @@ Windmill Community Edition {GIT_VERSION} display_config(&ENV_SETTINGS); + if let Err(e) = reload_base_url_setting(&db).await { + tracing::error!("Error loading base url: {:?}", e) + } + + if let Err(e) = reload_critical_error_channels_setting(&db).await { + tracing::error!("Could loading critical error emails setting: {:?}", e); + } + + #[cfg(feature = "enterprise")] + { + // load the license key and check if it's valid + // if not valid and not server mode just quit + // if not expired and server mode then force renewal + // if key still invalid and num_workers > 0, set to 0 + if let Err(err) = reload_license_key(&db).await { + tracing::error!("Failed to reload license key: {err:#}"); + } + let valid_key = *LICENSE_KEY_VALID.read().await; + if !valid_key && !server_mode { + panic!("Invalid license key, workers require a valid license key"); + } + if server_mode { + // only force renewal if invalid but not empty (= expired) + let renewed_now = maybe_renew_license_key_on_start( + &HTTP_CLIENT, + &db, + !valid_key && !LICENSE_KEY_ID.read().await.is_empty(), + ) + .await; + if renewed_now { + if let Err(err) = reload_license_key(&db).await { + tracing::error!("Failed to reload license key: {err:#}"); + } + } + if num_workers > 0 { + let valid_key = *LICENSE_KEY_VALID.read().await; + if !valid_key { + tracing::warn!("License key invalid, setting num_workers to 0"); + num_workers = 0; + } + } + } + } + let worker_mode = num_workers > 0; if server_mode || worker_mode || indexer_mode { @@ -448,7 +493,16 @@ Windmill Community Edition {GIT_VERSION} initial_load(&db, killpill_tx.clone(), worker_mode, server_mode, is_agent).await; - monitor_db(&db, &base_internal_url, rsmq.clone(), server_mode, true).await; + monitor_db( + &db, + &base_internal_url, + rsmq.clone(), + server_mode, + worker_mode, + true, + killpill_tx.clone(), + ) + .await; monitor_pool(&db).await; @@ -548,8 +602,11 @@ Windmill Community Edition {GIT_VERSION} rx.recv().await?; } } - tracing::info!("Starting phase 2 of shutdown"); - killpill_phase2_tx.send(())?; + if killpill_phase2_tx.receiver_count() > 0 { + tracing::info!("Starting phase 2 of shutdown"); + killpill_phase2_tx.send(())?; + tracing::info!("Phase 2 of shutdown completed"); + } Ok(()) as anyhow::Result<()> }; @@ -575,7 +632,9 @@ Windmill Community Edition {GIT_VERSION} &base_internal_url, rsmq.clone(), server_mode, - false + worker_mode, + false, + tx.clone(), ) .await; }, @@ -618,7 +677,15 @@ Windmill Community Edition {GIT_VERSION} }, LICENSE_KEY_SETTING => { if let Err(e) = reload_license_key(&db).await { - tracing::error!(error = %e, "Could not reload license key setting"); + tracing::error!("Failed to reload license key: {e:#}"); + } + #[cfg(feature = "enterprise")] + if worker_mode { + let valid_key = *LICENSE_KEY_VALID.read().await; + if !valid_key { + tracing::error!("Invalid license key, exiting..."); + tx.send(()).expect("send"); + } } }, DEFAULT_TAGS_PER_WORKSPACE_SETTING => { @@ -764,15 +831,10 @@ Windmill Community Edition {GIT_VERSION} Ok(()) as anyhow::Result<()> }; - if mode == Mode::Server || mode == Mode::Standalone { + if server_mode { schedule_stats(&db, &HTTP_CLIENT).await; } - #[cfg(feature = "enterprise")] - if mode == Mode::Server || mode == Mode::Standalone { - schedule_key_renewal(&HTTP_CLIENT, &db).await; - } - futures::try_join!( shutdown_signal, workers_f, diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 576a38de72..0618cfd8d8 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -27,7 +27,7 @@ use windmill_api::{ DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT, SAML_METADATA, SCIM_TOKEN, }; #[cfg(feature = "enterprise")] -use windmill_common::ee::{worker_groups_alerts, jobs_waiting_alerts}; +use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts, LICENSE_KEY_VALID}; use windmill_common::{ auth::JWT_SECRET, ee::CriticalErrorChannel, @@ -76,9 +76,6 @@ use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING; #[cfg(feature = "enterprise")] use crate::ee::verify_license_key; -#[cfg(feature = "enterprise")] -use windmill_common::ee::LICENSE_KEY_VALID; - use crate::ee::set_license_key; lazy_static::lazy_static! { @@ -152,18 +149,10 @@ pub async fn initial_load( tracing::error!("Error reloading custom tags: {:?}", e) } - if let Err(e) = reload_base_url_setting(db).await { - tracing::error!("Error reloading base url: {:?}", e) - } - if let Err(e) = reload_hub_base_url_setting(db, server_mode).await { tracing::error!("Error reloading hub base url: {:?}", e) } - if let Err(e) = reload_critical_error_channels_setting(&db).await { - tracing::error!("Could not reload critical error emails setting: {:?}", e); - } - if let Err(e) = reload_jwt_secret_setting(&db).await { tracing::error!("Could not reload jwt secret setting: {:?}", e); } @@ -182,11 +171,6 @@ pub async fn initial_load( reload_scim_token_setting(&db).await; } - #[cfg(feature = "enterprise")] - if let Err(e) = reload_license_key(&db).await { - tracing::error!("Error reloading license key: {:?}", e) - } - if worker_mode { reload_extra_pip_index_url_setting(&db).await; reload_pip_index_url_setting(&db).await; @@ -854,8 +838,10 @@ pub async fn reload_request_size(db: &DB) { } } -pub async fn reload_license_key(db: &DB) -> error::Result<()> { - let q = load_value_from_global_settings(db, LICENSE_KEY_SETTING).await?; +pub async fn reload_license_key(db: &DB) -> anyhow::Result<()> { + let q = load_value_from_global_settings(db, LICENSE_KEY_SETTING) + .await + .map_err(|err| anyhow::anyhow!("Error reloading license key: {}", err.to_string()))?; let mut value = std::env::var("LICENSE_KEY") .ok() @@ -873,9 +859,7 @@ pub async fn reload_license_key(db: &DB) -> error::Result<()> { tracing::error!("Could not parse LICENSE_KEY found: {:#?}", &q); } }; - - set_license_key(value).await?; - + set_license_key(value).await; Ok(()) } @@ -1014,7 +998,9 @@ pub async fn monitor_db( base_internal_url: &str, rsmq: Option, server_mode: bool, + _worker_mode: bool, initial_load: bool, + _killpill_tx: tokio::sync::broadcast::Sender<()>, ) { let zombie_jobs_f = async { if server_mode && !initial_load { @@ -1035,15 +1021,14 @@ pub async fn monitor_db( let verify_license_key_f = async { #[cfg(feature = "enterprise")] - if let Err(e) = verify_license_key().await { - tracing::error!("Error verifying license key: {:?}", e); - let mut l = LICENSE_KEY_VALID.write().await; - *l = false; - } else { - let is_valid = LICENSE_KEY_VALID.read().await.clone(); - if !is_valid { - let mut l = LICENSE_KEY_VALID.write().await; - *l = true; + if !initial_load { + verify_license_key().await; + if _worker_mode { + let valid_key = *LICENSE_KEY_VALID.read().await; + if !valid_key { + tracing::error!("Invalid license key, exiting..."); + _killpill_tx.send(()).expect("send"); + } } } }; @@ -1068,6 +1053,15 @@ pub async fn monitor_db( } }; + let apply_autoscaling_f = async { + #[cfg(feature = "enterprise")] + if server_mode && !initial_load { + if let Err(e) = windmill_autoscaling::apply_all_autoscaling(db).await { + tracing::error!("Error applying autoscaling: {:?}", e); + } + } + }; + join!( expired_items_f, zombie_jobs_f, @@ -1075,6 +1069,7 @@ pub async fn monitor_db( verify_license_key_f, worker_groups_alerts_f, jobs_waiting_alerts_f, + apply_autoscaling_f, ); } @@ -1092,19 +1087,11 @@ pub async fn expose_queue_metrics(db: &Pool) { .unwrap_or(true); if metrics_enabled || save_metrics { - let queue_counts = sqlx::query!( - "SELECT tag, count(*) as count FROM queue WHERE - scheduled_for <= now() - ('3 seconds')::interval AND running = false - GROUP BY tag" - ) - .fetch_all(db) - .await - .ok() - .unwrap_or_else(|| vec![]); + let queue_counts = windmill_common::queue::get_queue_counts(db).await; for q in queue_counts { - let count = q.count.unwrap_or(0); - let tag = q.tag; + let count = q.1; + let tag = q.0; if metrics_enabled { let metric = (*QUEUE_COUNT).with_label_values(&[&tag]); metric.set(count as i64); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 969bdb0cac..6175aa3572 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.411.1 + version: 1.414.1 title: Windmill API contact: @@ -1467,6 +1467,11 @@ paths: parameters: - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" + - name: active_only + in: query + description: filter only active users + schema: + type: boolean responses: "200": description: user @@ -8198,6 +8203,29 @@ paths: items: $ref: "#/components/schemas/Config" + /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 @@ -11768,6 +11796,8 @@ components: type: string username: type: string + operator_only: + type: boolean required: - email @@ -12469,3 +12499,21 @@ components: 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 diff --git a/backend/windmill-api/src/configs.rs b/backend/windmill-api/src/configs.rs index 64c8a1884f..81b65216e0 100644 --- a/backend/windmill-api/src/configs.rs +++ b/backend/windmill-api/src/configs.rs @@ -29,6 +29,10 @@ pub fn global_service() -> Router { .route("/update/:name", post(update_config).delete(delete_config)) .route("/get/:name", get(get_config)) .route("/list", get(list_configs)) + .route( + "/list_autoscaling_events/:worker_group", + get(list_autoscaling_events), + ) } #[derive(Serialize, Deserialize, FromRow)] @@ -177,6 +181,30 @@ async fn delete_config( Ok(format!("Deleted config {name}")) } +#[derive(Serialize, Deserialize, FromRow)] +struct AutoscalingEvent { + id: i64, + worker_group: String, + event_type: Option, + desired_workers: i32, + reason: Option, + applied_at: chrono::NaiveDateTime, +} + +async fn list_autoscaling_events( + Extension(db): Extension, + Path(worker_group): Path, +) -> error::JsonResult> { + let events = sqlx::query_as!( + AutoscalingEvent, + "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT 5", + worker_group + ) + .fetch_all(&db) + .await?; + Ok(Json(events)) +} + #[cfg(feature = "enterprise")] async fn list_configs( authed: ApiAuthed, diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index c44b8c18e6..6186b0ada1 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -173,17 +173,6 @@ pub async fn migrate(db: &DB) -> Result<(), Error> { let migrator = db.acquire().await?; let mut custom_migrator = CustomMigrator { inner: migrator }; - if let Err(err) = fix_flow_versioning_migration(&mut custom_migrator, db).await { - tracing::error!("Could not apply flow versioning fix migration: {err:#}"); - } - - let db2 = db.clone(); - let _ = tokio::task::spawn(async move { - if let Err(err) = fix_job_completed_index(&db2).await { - tracing::error!("Could not apply job completed index fix migration: {err:#}"); - } - }); - match sqlx::migrate!("../migrations") .run_direct(&mut custom_migrator) .await @@ -199,6 +188,17 @@ pub async fn migrate(db: &DB) -> Result<(), Error> { Err(err) => Err(err), }?; + if let Err(err) = fix_flow_versioning_migration(&mut custom_migrator, db).await { + tracing::error!("Could not apply flow versioning fix migration: {err:#}"); + } + + let db2 = db.clone(); + let _ = tokio::task::spawn(async move { + if let Err(err) = fix_job_completed_index(&db2).await { + tracing::error!("Could not apply job completed index fix migration: {err:#}"); + } + }); + Ok(()) } @@ -302,7 +302,7 @@ macro_rules! run_windmill_migration { .await?; tracing::info!("Finished applying {migration_job_name} migration"); } else { - tracing::info!("migration {migration_job_name} already done"); + tracing::debug!("migration {migration_job_name} already done"); } let _ = sqlx::query("SELECT pg_advisory_unlock(4242)") @@ -311,7 +311,7 @@ macro_rules! run_windmill_migration { tx.commit().await?; tracing::info!("released lock for {migration_job_name}"); } else { - tracing::info!("migration {migration_job_name} already done"); + tracing::debug!("migration {migration_job_name} already done"); } } diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee.rs index b20521e8b4..cddb639e95 100644 --- a/backend/windmill-api/src/ee.rs +++ b/backend/windmill-api/src/ee.rs @@ -4,7 +4,7 @@ use std::sync::Arc; #[cfg(feature = "enterprise")] use tokio::sync::RwLock; -pub async fn validate_license_key(_license_key: String) -> anyhow::Result { +pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> { // Implementation is not open source Err(anyhow!("License can't be validated in Windmill CE")) } diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 3644e04d30..4fc9ce514f 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -544,7 +544,6 @@ async fn get_latest_version( Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, ) -> JsonResult> { - let path = path.to_path(); let mut tx = user_db.begin(&authed).await?; @@ -1333,7 +1332,6 @@ mod tests { }, "type": "script", "path": "test", - "tag_override": Option::::None, }, }, { @@ -1377,14 +1375,12 @@ mod tests { "input_transforms": {}, "type": "script", "path": "test", - "tag_override": Option::::None, }, "stop_after_if": { "expr": "previous.isEmpty()", "skip_if_stopped": false } }, - "preprocessor_module": Option::::None }); assert_eq!(dbg!(serde_json::json!(fv)), dbg!(expect)); } diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 53cd958c6f..7244b2be17 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -2815,7 +2815,6 @@ pub async fn run_flow_by_path_inner( let flow_path = flow_path.to_path(); check_scopes(&authed, || format!("run:flow/{flow_path}"))?; - let (tag, dedicated_worker, has_preprocessor) = sqlx::query!( "SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor FROM flow @@ -2910,7 +2909,6 @@ pub async fn restart_flow( ) -> error::Result<(StatusCode, String)> { check_license_key_valid().await?; - let completed_job = sqlx::query_as::<_, CompletedJob>( "SELECT *, result->'wm_labels' as labels from completed_job WHERE id = $1 and workspace_id = $2", ) @@ -3010,7 +3008,6 @@ pub async fn run_script_by_path_inner( check_scopes(&authed, || format!("run:script/{script_path}"))?; - let (job_payload, tag, _delete_after_use, timeout) = script_path_to_payload(script_path, &db, &w_id, run_query.skip_preprocessor).await?; let scheduled_for = run_query.get_scheduled_for(&db).await?; @@ -3018,7 +3015,6 @@ pub async fn run_script_by_path_inner( let tag = run_query.tag.clone().or(tag); check_tag_available_for_workspace(&w_id, &tag).await?; - let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq); let (uuid, tx) = push( @@ -3067,7 +3063,6 @@ pub async fn run_workflow_as_code( Query(wkflow_query): Query, Json(task): Json, ) -> error::Result<(StatusCode, String)> { - let mut i = 1; if *CLOUD_HOSTED { @@ -3079,23 +3074,18 @@ pub async fn run_workflow_as_code( check_license_key_valid().await?; check_tag_available_for_workspace(&w_id, &run_query.tag).await?; - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; } - let job = get_queued_job(&job_id, &w_id, &db).await?; - - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; } - let job = not_found_if_none(job, "Queued Job", &job_id.to_string())?; let (job_payload, tag, _delete_after_use, timeout) = match job.job_kind { JobKind::Preview => ( @@ -3118,18 +3108,12 @@ pub async fn run_workflow_as_code( run_query.timeout, ), JobKind::Script => { - script_path_to_payload( - job.script_path(), - &db, - &w_id, - run_query.skip_preprocessor, - ) - .await? + script_path_to_payload(job.script_path(), &db, &w_id, run_query.skip_preprocessor) + .await? } _ => return Err(anyhow::anyhow!("Not supported").into()), }; - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; @@ -3143,22 +3127,18 @@ pub async fn run_workflow_as_code( let tag = run_query.tag.clone().or(tag).or(Some(job.tag)); - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; } - let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq); - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; } - let (uuid, mut tx) = push( &db, tx, @@ -3185,7 +3165,6 @@ pub async fn run_workflow_as_code( ) .await?; - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; @@ -3203,16 +3182,13 @@ pub async fn run_workflow_as_code( tracing::info!("Skipping update of flow status for job {job_id} in workspace {w_id}"); } - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); i += 1; } - tx.commit().await?; - if *CLOUD_HOSTED { tracing::info!("workflow_as_code_tracing id {i} "); } @@ -3866,7 +3842,6 @@ pub async fn run_wait_result_flow_by_path_internal( let flow_path = flow_path.to_path(); check_scopes(&authed, || format!("run:flow/{flow_path}"))?; - let scheduled_for = run_query.get_scheduled_for(&db).await?; let (tag, dedicated_worker, early_return, has_preprocessor) = sqlx::query!( @@ -4362,7 +4337,6 @@ 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 } @@ -4580,7 +4554,6 @@ pub async fn run_job_by_hash_inner( #[cfg(feature = "enterprise")] check_license_key_valid().await?; - let hash = script_hash.0; let ( path, diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 45a0ea7196..dc9dbd2529 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -456,9 +456,13 @@ async fn ee_license() -> &'static str { #[cfg(feature = "enterprise")] async fn ee_license() -> String { - use windmill_common::ee::LICENSE_KEY_ID; + use windmill_common::ee::{LICENSE_KEY_ID, LICENSE_KEY_VALID}; - LICENSE_KEY_ID.read().await.clone() + if *LICENSE_KEY_VALID.read().await { + LICENSE_KEY_ID.read().await.clone() + } else { + "".to_string() + } } async fn openapi() -> &'static str { diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 8a72a5459a..e8c000c788 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -163,8 +163,13 @@ pub async fn test_license_key( Json(TestKey { license_key }): Json, ) -> error::Result { require_super_admin(&db, &authed.email).await?; - validate_license_key(license_key).await?; - Ok("Sent test email".to_string()) + let (_, expired) = validate_license_key(license_key).await?; + + if expired { + Err(error::Error::BadRequest("Expired license key".to_string())) + } else { + Ok("Valid license key".to_string()) + } } pub async fn get_local_settings( @@ -301,7 +306,6 @@ pub async fn send_stats(Extension(db): Extension, authed: ApiAuthed) -> Resu windmill_common::stats_ee::send_stats( &HTTP_CLIENT, &db, - true, windmill_common::stats_ee::SendStatsReason::Manual, ) .await?; diff --git a/backend/windmill-autoscaling/Cargo.toml b/backend/windmill-autoscaling/Cargo.toml new file mode 100644 index 0000000000..7aada6f904 --- /dev/null +++ b/backend/windmill-autoscaling/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "windmill-autoscaling" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_autoscaling" +path = "./src/lib.rs" + +[features] +enterprise = ["windmill-queue/enterprise", "windmill-common/enterprise"] +default = [] + +[dependencies] +uuid.workspace = true +serde.workspace = true +sqlx.workspace = true +serde_json.workspace = true +tracing.workspace = true +windmill-common = { workspace = true, default-features = false } +windmill-queue.workspace = true +rsmq_async.workspace = true +anyhow.workspace = true \ No newline at end of file diff --git a/backend/windmill-autoscaling/src/autoscaling_ee.rs b/backend/windmill-autoscaling/src/autoscaling_ee.rs new file mode 100644 index 0000000000..1c9defbede --- /dev/null +++ b/backend/windmill-autoscaling/src/autoscaling_ee.rs @@ -0,0 +1,6 @@ +use windmill_common::DB; + +pub async fn apply_all_autoscaling(_db: &DB) -> anyhow::Result<()> { + // Autoscaling is an ee feature + Ok(()) +} diff --git a/backend/windmill-autoscaling/src/lib.rs b/backend/windmill-autoscaling/src/lib.rs new file mode 100644 index 0000000000..28b9319244 --- /dev/null +++ b/backend/windmill-autoscaling/src/lib.rs @@ -0,0 +1,2 @@ +mod autoscaling_ee; +pub use autoscaling_ee::*; diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee.rs index 6df93aca78..e25d36bee1 100644 --- a/backend/windmill-common/src/ee.rs +++ b/backend/windmill-common/src/ee.rs @@ -48,8 +48,13 @@ pub async fn send_critical_alert( } #[cfg(feature = "enterprise")] -pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () { +pub async fn maybe_renew_license_key_on_start( + _http_client: &reqwest::Client, + _db: &crate::db::DB, + force_renew_now: bool, +) -> bool { // Implementation is not open source + force_renew_now } #[cfg(feature = "enterprise")] diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 958e9f13ef..9b66cc65ac 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -101,6 +101,7 @@ pub struct FlowValue { #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] pub failure_module: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] pub preprocessor_module: Option>, #[serde(default)] @@ -423,6 +424,7 @@ pub enum FlowModuleValue { path: String, #[serde(skip_serializing_if = "Option::is_none")] hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] tag_override: Option, }, Flow { diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 0f25e12e46..bcac7b85fb 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -17,6 +17,7 @@ use scripts::ScriptLang; use sqlx::{Pool, Postgres}; pub mod apps; +pub mod auth; #[cfg(feature = "benchmark")] pub mod bench; pub mod db; @@ -32,9 +33,8 @@ pub mod job_s3_helpers_ee; pub mod jobs; pub mod more_serde; pub mod oauth2; +pub mod queue; pub mod s3_helpers; - -pub mod auth; pub mod schedule; pub mod scripts; pub mod server; diff --git a/backend/windmill-common/src/queue.rs b/backend/windmill-common/src/queue.rs new file mode 100644 index 0000000000..6cbb5611c5 --- /dev/null +++ b/backend/windmill-common/src/queue.rs @@ -0,0 +1,16 @@ +use std::collections::HashMap; + +use sqlx::{Pool, Postgres}; + +pub async fn get_queue_counts(db: &Pool) -> HashMap { + sqlx::query_as::<_, (String, i64)>( + "SELECT tag, count(*) as count FROM queue WHERE + scheduled_for <= now() - ('3 seconds')::interval AND running = false + GROUP BY tag", + ) + .fetch_all(db) + .await + .ok() + .map(|v| v.into_iter().map(|(k, v)| (k, v as u32)).collect()) + .unwrap_or_else(|| HashMap::new()) +} diff --git a/backend/windmill-common/src/stats_ee.rs b/backend/windmill-common/src/stats_ee.rs index c6dc9946bf..5d2dc82b82 100644 --- a/backend/windmill-common/src/stats_ee.rs +++ b/backend/windmill-common/src/stats_ee.rs @@ -28,7 +28,6 @@ pub enum SendStatsReason { pub async fn send_stats( _http_client: &reqwest::Client, _db: &DB, - _skip_job_usage: bool, _reason: SendStatsReason, ) -> Result<()> { // stats details are closed source diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index d5f9c5c099..3feab853dd 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -206,6 +206,14 @@ pub async fn get_uid<'c, E: sqlx::Executor<'c, Database = Postgres>>(db: E) -> R Ok(uid) } +pub fn map_string_to_number(s: &str, max_number: u64) -> u64 { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + s.hash(&mut hasher); + hasher.finish() % (max_number + 1) +} + #[derive(Serialize, Deserialize, PartialEq, Clone)] #[serde(rename_all = "lowercase")] pub enum Mode { diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index deabe07c62..4f23fe1f7d 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -23,7 +23,7 @@ use windmill_common::{ jobs::QueuedJob, worker::{to_raw_value, write_file, write_file_at_user_defined_location, WORKER_CONFIG}, }; -use windmill_parser_yaml::AnsibleRequirements; +use windmill_parser_yaml::{AnsibleRequirements, ResourceOrVariablePath}; use windmill_queue::{append_logs, CanceledBy}; use crate::{ @@ -186,7 +186,7 @@ async fn install_galaxy_collections( #[cfg(not(feature = "enterprise"))] fn check_ansible_exists() -> Result<(), error::Error> { if !Path::new(ANSIBLE_PLAYBOOK_PATH.as_str()).exists() { - let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run rust jobs.", ANSIBLE_PLAYBOOK_PATH.as_str()); + let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run Ansible jobs.", ANSIBLE_PLAYBOOK_PATH.as_str()); return Err(error::Error::NotFound(msg)); } Ok(()) @@ -195,7 +195,7 @@ fn check_ansible_exists() -> Result<(), error::Error> { #[cfg(feature = "enterprise")] fn check_ansible_exists() -> Result<(), error::Error> { if !Path::new(ANSIBLE_PLAYBOOK_PATH.as_str()).exists() { - let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run rust jobs.", ANSIBLE_PLAYBOOK_PATH.as_str()); + let msg = format!("Couldn't find ansible-playbook at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run Ansible jobs.", ANSIBLE_PLAYBOOK_PATH.as_str()); return Err(error::Error::NotFound(msg)); } Ok(()) @@ -557,20 +557,17 @@ async fn create_file_resources( } for file_res in &r.file_resources { - let r = client - .get_resource_value_interpolated::( - &file_res.resource_path, - Some(job_id.to_string()), - ) - .await?; + let r = get_resource_or_variable_content( + client, + &file_res.resource_path, + job_id.to_string(), + ) + .await?; let path = file_res.target_path.clone(); let validated_path = write_file_at_user_defined_location( job_dir, path.as_str(), - r.get("content").and_then(|v| v.as_str()).ok_or(anyhow!( - "Invalid text file resource {}, `content` field absent or invalid", - &file_res.resource_path - ))?, + &r, ) .map_err(|e| anyhow!("Couldn't write text file at {}: {}", path, e))?; @@ -580,7 +577,7 @@ async fn create_file_resources( ); logs.push_str(&format!( - "\nCreated {} from {}", + "\nCreated {} from {:?}", file_res.target_path, file_res.resource_path )); } @@ -588,3 +585,25 @@ async fn create_file_resources( Ok(nsjail_mounts) } + +async fn get_resource_or_variable_content( + client: &crate::AuthedClient, + path: &ResourceOrVariablePath, + job_id: String, +) -> anyhow::Result { + Ok(match path { + ResourceOrVariablePath::Resource(p) => { + let r = client + .get_resource_value_interpolated::(&p, Some(job_id)) + .await?; + + r.get("content").and_then(|v| v.as_str()).ok_or(anyhow!( + "Invalid text file resource {}, `content` field absent or invalid", + p + ))?.to_string() + } + ResourceOrVariablePath::Variable(p) => { + client.get_variable_value(&p).await? + } + }) +} diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 128eb7d25f..ed7a1dd0f6 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -59,11 +59,42 @@ pub async fn handle_bash_job( append_logs(&job.id, &job.workspace_id, logs1, db).await; write_file(job_dir, "main.sh", &format!("set -e\n{content}"))?; - write_file( - job_dir, - "wrapper.sh", - &format!("set -o pipefail\nset -e\nmkfifo bp\ncat bp | tail -1 > ./result2.out &\n {bash} ./main.sh \"$@\" 2>&1 | tee bp\nwait $!", bash = BIN_BASH.as_str()), - )?; + let script = format!( + r#" +set -o pipefail +set -e + +# Function to kill child processes +cleanup() {{ + echo "Terminating child processes..." + + # Ignore SIGTERM and SIGINT + trap '' SIGTERM SIGINT + + # Kill the process group of the script (negative PID value) + pkill -P $$ + exit +}} + + +# Trap SIGTERM (or other signals) and call cleanup function +trap cleanup SIGTERM SIGINT + +# Create a named pipe +mkfifo bp + +# Start background processes +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 +"#, + bash = BIN_BASH.as_str(), + ); + write_file(job_dir, "wrapper.sh", &script)?; let token = client.get_token().await; let mut reserved_variables = get_reserved_variables(job, &token, db).await?; diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 0721918568..4a8c0749bc 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -63,8 +63,26 @@ const RELATIVE_BUN_BUILDER: &str = include_str!("../loader_builder.bun.js"); const NSJAIL_CONFIG_RUN_BUN_CONTENT: &str = include_str!("../nsjail/run.bun.config.proto"); pub const BUN_LOCKB_SPLIT: &str = "\n//bun.lockb\n"; +pub const BUN_LOCKB_SPLIT_WINDOWS: &str = "\r\n//bun.lockb\r\n"; + pub const EMPTY_FILE: &str = ""; +fn split_lockfile(lockfile: &str) -> (&str, Option<&str>, bool) { + if let Some(index) = lockfile.find(BUN_LOCKB_SPLIT) { + // Split using "\n//bun.lockb\n" + let (before, after_with_sep) = lockfile.split_at(index); + let after = &after_with_sep[BUN_LOCKB_SPLIT.len()..]; + (before, Some(after), after == EMPTY_FILE) + } else if let Some(index) = lockfile.find(BUN_LOCKB_SPLIT_WINDOWS) { + // Split using "\r\n//bun.lockb\r\n" + let (before, after_with_sep) = lockfile.split_at(index); + let after = &after_with_sep[BUN_LOCKB_SPLIT_WINDOWS.len()..]; + (before, Some(after), after == EMPTY_FILE) + } else { + (lockfile, None, false) + } +} + pub async fn gen_bun_lockfile( mem_peak: &mut i32, canceled_by: &mut Option, @@ -180,7 +198,12 @@ pub async fn gen_bun_lockfile( file.read_to_string(&mut content).await?; } if !npm_mode { + #[cfg(any(target_os = "linux", target_os = "macos"))] content.push_str(BUN_LOCKB_SPLIT); + + #[cfg(target_os = "windows")] + content.push_str(BUN_LOCKB_SPLIT_WINDOWS); + { let file = format!("{job_dir}/bun.lockb"); if !empty_deps && tokio::fs::metadata(&file).await.is_ok() { @@ -762,12 +785,15 @@ async fn compute_bundle_local_and_remote_path( } pub async fn prepare_job_dir(reqs: &str, job_dir: &str) -> Result<()> { - let splitted = reqs.split(BUN_LOCKB_SPLIT).collect::>(); - let _ = write_file(job_dir, "package.json", &splitted[0])?; + let (pkg, lock, empty) = split_lockfile(reqs); + let _ = write_file(job_dir, "package.json", pkg)?; - if splitted[1] != EMPTY_FILE { - let _ = write_lockb(splitted[1], job_dir).await?; + if !empty { + if let Some(lock) = lock { + let _ = write_lockb(lock, job_dir).await?; + } } + Ok(()) } async fn write_lockb(splitted_lockb_2: &str, job_dir: &str) -> Result<()> { @@ -874,22 +900,23 @@ pub async fn handle_bun_job( } else if let Some(codebase) = codebase.as_ref() { pull_codebase(&job.workspace_id, codebase, job_dir).await?; } else if let Some(reqs) = requirements_o.as_ref() { - let splitted = reqs.split(BUN_LOCKB_SPLIT).collect::>(); - if splitted.len() != 2 && !annotation.npm { + let (pkg, lock, empty) = split_lockfile(reqs); + + if lock.is_none() && !annotation.npm { return Err(error::Error::ExecutionErr( format!("Invalid requirements, expected to find //bun.lockb split pattern in reqs. Found: |{reqs}|") )); } - let _ = write_file(job_dir, "package.json", &splitted[0])?; - let lockb = if annotation.npm { "" } else { splitted[1] }; - if lockb != EMPTY_FILE { + let _ = write_file(job_dir, "package.json", pkg)?; + let lockb = if annotation.npm { "" } else { lock.unwrap() }; + if !empty { let mut skip_install = false; let mut create_buntar = false; let mut buntar_path = "".to_string(); if !annotation.npm { - let _ = write_lockb(&splitted[1], job_dir).await?; + let _ = write_lockb(lockb, job_dir).await?; let mut sha_path = sha2::Sha256::new(); sha_path.update(lockb.as_bytes()); @@ -1561,20 +1588,20 @@ pub async fn start_worker( if let Some(codebase) = codebase.as_ref() { pull_codebase(w_id, codebase, job_dir).await?; } else if let Some(reqs) = requirements_o { - let splitted = reqs.split(BUN_LOCKB_SPLIT).collect::>(); - if splitted.len() != 2 { + let (pkg, lock, empty) = split_lockfile(&reqs); + if lock.is_none() { return Err(error::Error::ExecutionErr( format!("Invalid requirements, expected to find //bun.lockb split pattern in reqs. Found: |{reqs}|") )); } - let _ = write_file(job_dir, "package.json", &splitted[0])?; - let lockb = splitted[1]; - if lockb != EMPTY_FILE { + let _ = write_file(job_dir, "package.json", pkg)?; + let lockb = lock.unwrap(); + if !empty { let _ = write_file_binary( job_dir, "bun.lockb", &base64::engine::general_purpose::STANDARD - .decode(&splitted[1]) + .decode(lockb) .map_err(|_| { error::Error::InternalErr("Could not decode bun.lockb".to_string()) })?, diff --git a/backend/windmill-worker/src/pg_executor.rs b/backend/windmill-worker/src/pg_executor.rs index 7702b73b03..f27eeee12b 100644 --- a/backend/windmill-worker/src/pg_executor.rs +++ b/backend/windmill-worker/src/pg_executor.rs @@ -85,7 +85,7 @@ fn do_postgresql_inner<'a>( let arg_t = arg .otyp .as_ref() - .ok_or_else(|| anyhow::anyhow!("Missing otyp for pg arg"))?; + .ok_or_else(|| anyhow::anyhow!("Missing otzyp for pg arg"))?; let typ = &arg.typ; let param = convert_val(value, arg_t, typ)?; query_params.push(param); diff --git a/backend/windmill-worker/src/php_executor.rs b/backend/windmill-worker/src/php_executor.rs index b16e25f8ae..194e621fe1 100644 --- a/backend/windmill-worker/src/php_executor.rs +++ b/backend/windmill-worker/src/php_executor.rs @@ -143,7 +143,7 @@ fn check_php_exists() -> error::Result<()> { #[cfg(feature = "enterprise")] fn check_php_exists() -> error::Result<()> { if !Path::new(PHP_PATH.as_str()).exists() { - let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run php jobs.", PHP_PATH.as_str()); + let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run php jobs.", PHP_PATH.as_str()); return Err(error::Error::NotFound(msg)); } Ok(()) diff --git a/backend/windmill-worker/src/rust_executor.rs b/backend/windmill-worker/src/rust_executor.rs index 6a0090ab55..530be394ff 100644 --- a/backend/windmill-worker/src/rust_executor.rs +++ b/backend/windmill-worker/src/rust_executor.rs @@ -281,7 +281,7 @@ fn check_cargo_exists() -> Result<(), Error> { #[cfg(feature = "enterprise")] fn check_cargo_exists() -> Result<(), Error> { if !Path::new(CARGO_PATH.as_str()).exists() { - let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run rust jobs.", CARGO_PATH.as_str()); + let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-ee-full` for your instance in order to run rust jobs.", CARGO_PATH.as_str()); return Err(Error::NotFound(msg)); } Ok(()) diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 3f92cfc829..a1a4fd9904 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -2403,15 +2403,15 @@ async fn push_next_flow_job } tracing::debug!(id = %flow_job.id, root_id = %job_root, "pushing job {i} of {len}"); let payload_tag = match &job_payloads { - ContinuePayload::SingleJob(payload) => payload.clone(), - ContinuePayload::BranchAllJobs(payloads) => payloads[i].clone(), + ContinuePayload::SingleJob(payload) => payload, + ContinuePayload::BranchAllJobs(payloads) => &payloads[i], ContinuePayload::ForloopJobs { flow_value, delete_after_use, .. } => { let mut fv = flow_value.clone(); if let Some(failure_module) = fv.failure_module.as_mut() { failure_module.id_append(&format!("{}-{i}", &status.step.to_string())); } - JobPayloadWithTag { + &JobPayloadWithTag { payload: JobPayload::RawFlow { value: fv, path: Some(format!("{}/forloop-{i}", flow_job.script_path())), @@ -2590,13 +2590,17 @@ async fn push_next_flow_job }; tracing::debug!(id = %flow_job.id, root_id = %job_root, "computed perms for job {i} of {len}"); - + let tag = if flow_job.tag == "flow" || flow_job.tag == format!("flow-{}", flow_job.workspace_id) { + payload_tag.tag.clone() + } else { + Some(flow_job.tag.clone()) + }; let tx2 = PushIsolationLevel::Transaction(tx); let (uuid, mut inner_tx) = push( &db, tx2, &flow_job.workspace_id, - payload_tag.payload, + payload_tag.payload.clone(), push_args, &flow_job.created_by, &flow_job.email, @@ -2610,11 +2614,7 @@ async fn push_next_flow_job continue_on_same_worker, err, flow_job.visible_to_owner, - if flow_job.tag == "flow" || flow_job.tag == format!("flow-{}", flow_job.workspace_id) { - payload_tag.tag - } else { - Some(flow_job.tag.clone()) - }, + tag, payload_tag.timeout, Some(module.id.clone()), new_job_priority_override, @@ -3761,6 +3761,11 @@ async fn script_to_payload( module: &FlowModule, tag_override: &Option, ) -> Result { + let tag_override = if tag_override.as_ref().is_some_and(|x| x.trim().is_empty()) { + None + } else { + tag_override.clone() + }; let (payload, tag, delete_after_use, script_timeout) = if script_hash.is_none() { let (jp, tag, delete_after_use, script_timeout) = script_path_to_payload(script_path, db, &flow_job.workspace_id, Some(true)).await?; diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 44df40232b..9283f599df 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -291,7 +291,7 @@ pub async fn handle_dependency_job { return await windmill.UserService.login({ diff --git a/cli/instance.ts b/cli/instance.ts index bc3f0968b7..32117f8e3f 100644 --- a/cli/instance.ts +++ b/cli/instance.ts @@ -650,7 +650,10 @@ const command = new Command() .option("--skip-configs", "Skip pulling configs (worker groups and SMTP)") .option("--skip-groups", "Skip pulling instance groups") .option("--include-workspaces", "Also pull workspaces") - + .option( + "--instance ", + "Name of the instance to push to, override the active instance" + ) .action(instancePull as any) .command("push") .description( @@ -663,13 +666,9 @@ const command = new Command() .option("--skip-groups", "Skip pushing instance groups") .option("--include-workspaces", "Also push workspaces") .option( - "--instance", + "--instance ", "Name of the instance to push to, override the active instance" ) - .option( - "--base-url", - "If used with --token, will be used as the base url for the instance" - ) .action(instancePush as any) .command("whoami") .description("Display information about the currently logged-in user") diff --git a/cli/local_encryption.ts b/cli/local_encryption.ts index 0a70b0742b..8e7a241e52 100644 --- a/cli/local_encryption.ts +++ b/cli/local_encryption.ts @@ -3,102 +3,100 @@ import crypto from "node:crypto"; // Helper function to convert strings to Uint8Array (binary) function encode(input: string): Uint8Array { - return new TextEncoder().encode(input); - } - - // Helper function to convert Uint8Array (binary) to base64 - function toBase64(arr: Uint8Array): string { - return btoa(String.fromCharCode(...arr)); - } - - // Helper function to convert base64 to Uint8Array (binary) - function fromBase64(base64: string): Uint8Array { - return new Uint8Array( - atob(base64).split("").map((char) => char.charCodeAt(0)) - ); - } - - // Function to derive a 256-bit key from any input string using SHA-256 - async function deriveKey(keyString: string): Promise { - const keyMaterial = encode(keyString); - const keyHash = await crypto.subtle.digest("SHA-256", keyMaterial); // Generate SHA-256 hash - // Import the hash as a CryptoKey for AES-GCM - return crypto.subtle.importKey( - "raw", - keyHash, - { name: "AES-GCM" }, - false, - ["encrypt", "decrypt"] - ); - } + return new TextEncoder().encode(input); +} - - - // Encrypt function - export async function encrypt( - plaintext: string, - keyString: string - ): Promise { - const key = await deriveKey(keyString); // Derive a 256-bit AES key from any input string - const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM needs a 12-byte IV - const encrypted = await crypto.subtle.encrypt( - { - name: "AES-GCM", - iv, - tagLength: 128 - }, - key, - encode(plaintext) // convert plaintext to binary - ); - - // Concatenate IV and encrypted data - const combined = new Uint8Array(iv.length + encrypted.byteLength); - combined.set(iv, 0); // first part is the IV - combined.set(new Uint8Array(encrypted), iv.length); // second part is the ciphertext - - // Convert to base64 for storage/transmission - return toBase64(combined); - } - - // Decrypt function - export async function decrypt( - combinedCiphertext: string, - keyString: string - ): Promise { - const key = await deriveKey(keyString); // Derive the same 256-bit AES key from the input string - const combined = fromBase64(combinedCiphertext); // decode base64 to binary - - // Split the IV and the ciphertext - const iv = combined.slice(0, 12); // First 12 bytes are the IV - const ciphertext = combined.slice(12); // The rest is the encrypted data - console.log() +// Helper function to convert Uint8Array (binary) to base64 +function toBase64(arr: Uint8Array): string { + return btoa(String.fromCharCode(...arr)); +} + +// Helper function to convert base64 to Uint8Array (binary) +function fromBase64(base64: string): Uint8Array { + return new Uint8Array( + atob(base64) + .split("") + .map((char) => char.charCodeAt(0)) + ); +} + +// Function to derive a 256-bit key from any input string using SHA-256 +async function deriveKey( + keyString: string +): Promise { + const keyMaterial = encode(keyString); + const keyHash = await crypto.subtle.digest("SHA-256", keyMaterial); // Generate SHA-256 hash + // Import the hash as a CryptoKey for AES-GCM + return crypto.subtle.importKey("raw", keyHash, { name: "AES-GCM" }, false, [ + "encrypt", + "decrypt", + ]); +} + +// Encrypt function +export async function encrypt( + plaintext: string, + keyString: string +): Promise { + const key = await deriveKey(keyString); // Derive a 256-bit AES key from any input string + const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM needs a 12-byte IV + const encrypted = await crypto.subtle.encrypt( + { + name: "AES-GCM", + iv, + tagLength: 128, + }, + key, + encode(plaintext) // convert plaintext to binary + ); + + // Concatenate IV and encrypted data + const combined = new Uint8Array(iv.length + encrypted.byteLength); + combined.set(iv, 0); // first part is the IV + combined.set(new Uint8Array(encrypted), iv.length); // second part is the ciphertext + + // Convert to base64 for storage/transmission + return toBase64(combined); +} + +// Decrypt function +export async function decrypt( + combinedCiphertext: string, + keyString: string +): Promise { + const key = await deriveKey(keyString); // Derive the same 256-bit AES key from the input string + const combined = fromBase64(combinedCiphertext); // decode base64 to binary + + // Split the IV and the ciphertext + const iv = combined.slice(0, 12); // First 12 bytes are the IV + const ciphertext = combined.slice(12); // The rest is the encrypted data + console.log(); + + // log.info({keyString, key, ciphertext}) + // Perform decryption + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv, + tagLength: 128, + }, + key, + ciphertext + ); + + // Convert decrypted data from binary to string + return new TextDecoder().decode(decrypted); +} - // log.info({keyString, key, ciphertext}) - // Perform decryption - const decrypted = await crypto.subtle.decrypt( - { - name: "AES-GCM", - iv, - tagLength: 128 - }, - key, - ciphertext - ); - - // Convert decrypted data from binary to string - return new TextDecoder().decode(decrypted); - } - // // Example usage: // const key = "any-length-key-you-want"; // Now can be any length // const message = "This is a secret message."; - + // encrypt(message, key).then((combinedCiphertext) => { // console.log("Encrypted message:", combinedCiphertext); - + // // Now decrypt it // decrypt(combinedCiphertext, key).then((decryptedMessage) => { // console.log("Decrypted message:", decryptedMessage); // }); // }); - \ No newline at end of file diff --git a/cli/main.ts b/cli/main.ts index 0fc9384525..f23d96fcaa 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.411.1"; +export const VERSION = "1.414.1"; const command = new Command() .name("wmill") diff --git a/docker/DockerfileSlim b/docker/DockerfileSlim index a0190933a4..16c2361a0d 100644 --- a/docker/DockerfileSlim +++ b/docker/DockerfileSlim @@ -11,7 +11,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - -RUN apt-get -y update && apt-get install -y curl nodejs awscli +RUN apt-get -y update && apt-get install -y curl procps nodejs awscli ENV TZ=Etc/UTC diff --git a/docker/DockerfileSlimEe b/docker/DockerfileSlimEe index ef3771c767..65c6b55a19 100644 --- a/docker/DockerfileSlimEe +++ b/docker/DockerfileSlimEe @@ -11,7 +11,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - -RUN apt-get -y update && apt-get install -y curl nodejs awscli +RUN apt-get -y update && apt-get install -y curl procps nodejs awscli ENV TZ=Etc/UTC diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7761d4f4a6..89cb1179be 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.411.1", + "version": "1.414.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.411.1", + "version": "1.414.1", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", diff --git a/frontend/package.json b/frontend/package.json index dee26b8333..3d83a6b5c4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.411.1", + "version": "1.414.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/ArgInfo.svelte b/frontend/src/lib/components/ArgInfo.svelte index fb214640bd..dc6f8e153d 100644 --- a/frontend/src/lib/components/ArgInfo.svelte +++ b/frontend/src/lib/components/ArgInfo.svelte @@ -75,16 +75,18 @@ }}>{value} {:else if typeof value !== 'object'} - {truncate(JSON.stringify(value), 100)} - {#if JSON.stringify(value).length > 100} - - {/if} + + {truncate(JSON.stringify(value), 80)} + {#if JSON.stringify(value).length > 80} + + {/if} + {:else}
{#if JSON.stringify(value).length > 120} diff --git a/frontend/src/lib/components/AutoscalingConfigEditor.svelte b/frontend/src/lib/components/AutoscalingConfigEditor.svelte new file mode 100644 index 0000000000..3f763d835e --- /dev/null +++ b/frontend/src/lib/components/AutoscalingConfigEditor.svelte @@ -0,0 +1,364 @@ + + +
+
+
Rules
+ { + dispatch('dirty') + if (e.detail) { + if (!config) { + config = { + enabled: true, + min_workers: 3, + max_workers: 10, + integration: { type: 'dryrun' } + } + } else { + config.enabled = true + } + } else { + config = { + ...(config ?? { + min_workers: 3, + max_workers: 10, + integration: { type: 'dryrun' } + }), + enabled: false + } + } + }} + /> + + + + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+
+
+
Integration
+ {#if config?.integration} + dispatch('dirty')} + bind:selected={config.integration.type} + class="mb-4 mt-2" + > + + + + + + + + {#if config.integration.type === 'script'} + + + + +
+ + +
+
+ +
+ + workers +
+
+ {/if} + {:else} + + + + + + + + + + {/if} +
+
diff --git a/frontend/src/lib/components/AutoscalingEvents.svelte b/frontend/src/lib/components/AutoscalingEvents.svelte new file mode 100644 index 0000000000..36240cef4c --- /dev/null +++ b/frontend/src/lib/components/AutoscalingEvents.svelte @@ -0,0 +1,73 @@ + + +
+
Autoscaling events {#if $enterpriseLicense}(5 last) + +
+ {#if !$enterpriseLicense} +
Autoscaling is an EE feature
+ {:else if loading} + + {:else if events} + {#if events.length == 0} +
No events, is autoscaling set in the worker group config?
+ {:else} +
+ {#each events as event} +
+
{event.event_type} to {event.desired_workers}
+
{event.reason}
+
+
+ {/each} +
+ {/if} + {/if} +
diff --git a/frontend/src/lib/components/CustomPopover.svelte b/frontend/src/lib/components/CustomPopover.svelte index 8e933049cc..22d58b2813 100644 --- a/frontend/src/lib/components/CustomPopover.svelte +++ b/frontend/src/lib/components/CustomPopover.svelte @@ -9,6 +9,7 @@ export let disappearTimeout = 100 export let appearTimeout = 300 export let style: string | undefined = undefined + export let noPadding = false export let focusEl: HTMLElement | undefined = undefined @@ -73,7 +74,7 @@ use:popperContent={popperOptions} on:mouseenter={open} on:mouseleave={close} - class="z-[5001] border rounded-lg shadow-lg p-4 bg-surface" + class="z-[5001] border rounded-lg shadow-lg {noPadding ? '' : 'p-4'} bg-surface" >
diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 6da81b66d3..b04fbdf8dc 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -119,13 +119,21 @@ if (version === undefined) { return } - const flowVersion = await FlowService.getFlowLatestVersion({ - workspace: $workspaceStore!, - path: $pathStore - }) - - onLatest = version === flowVersion?.id + try { + if (initialPath && initialPath != '') { + const flowVersion = await FlowService.getFlowLatestVersion({ + workspace: $workspaceStore!, + path: initialPath + }) + onLatest = version === flowVersion?.id + } else { + onLatest = true + } + } catch (err) { + console.error('Error comparing versions', err) + onLatest = true + } } const dispatch = createEventDispatcher() @@ -277,9 +285,8 @@ } async function handleSaveFlow(deploymentMsg?: string) { - - await compareVersions(); - if (onLatest) { + await compareVersions() + if (onLatest || initialPath == '' || savedFlow?.draft_only) { // Handle directly await saveFlow(deploymentMsg) } else { @@ -294,28 +301,27 @@ open = true } } - async function syncWithDeployed(){ - const flow = await FlowService.getFlowByPath({ - workspace: $workspaceStore!, - path: $pathStore, - withStarredInfo: true - }) - deployedValue = { - ...flow, - starred: undefined, - id: undefined, - edited_at: undefined, - edited_by: undefined, - workspace_id: undefined, - archived: undefined, - same_worker: undefined, - visible_to_runner_only: undefined, - ws_error_handler_muted: undefined, - } - deployedBy = flow.edited_by + async function syncWithDeployed() { + const flow = await FlowService.getFlowByPath({ + workspace: $workspaceStore!, + path: initialPath, + withStarredInfo: true + }) + deployedValue = { + ...flow, + starred: undefined, + id: undefined, + edited_at: undefined, + edited_by: undefined, + workspace_id: undefined, + archived: undefined, + same_worker: undefined, + visible_to_runner_only: undefined, + ws_error_handler_muted: undefined + } + deployedBy = flow.edited_by } - async function saveFlow(deploymentMsg?: string): Promise { loadingSave = true try { diff --git a/frontend/src/lib/components/FlowGraphViewerStep.svelte b/frontend/src/lib/components/FlowGraphViewerStep.svelte index b2b7b9d381..fe235c6455 100644 --- a/frontend/src/lib/components/FlowGraphViewerStep.svelte +++ b/frontend/src/lib/components/FlowGraphViewerStep.svelte @@ -133,8 +133,10 @@ Error handler {:else if stepDetail.id === 'preprocessor'} Preprocessor - {:else} - Anonymous step + {:else if stepDetail.value.type == 'rawscript'} + Inline {stepDetail.value.language} script + {:else if stepDetail.value.type == 'script'} + Workspace script {/if} diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index ac0aabd161..8709a9f478 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -102,8 +102,8 @@ if (values['base_url'] == undefined) { values['base_url'] = 'http://localhost' } - if (values['smtp_connect'] == undefined) { - values['smtp_connect'] = {} + if (values['smtp_settings'] == undefined) { + values['smtp_settings'] = {} } loading = false @@ -206,7 +206,8 @@ 'basecamp', 'linkedin', 'quickbooks', - 'visma' + 'visma', + 'spotify' ] let oauth_name = undefined @@ -274,22 +275,34 @@ {#if category == 'SMTP'}
Setting SMTP unlocks sending emails upon adding new users to the workspace or the instance or sending critical alerts. - Learn more
Setting SMTP unlocks sending emails upon adding new users to the workspace or the + instance or sending critical alerts. + Learn more {:else if category == 'Registries'}
- Add private registries for Pip, Bun and npm. Learn more + Add private registries for Pip, Bun and npm. Learn more
{:else if category == 'Slack'}
- Connecting your instance to a Slack workspace enables critical alerts to be sent to a Slack channel. - Learn more + Connecting your instance to a Slack workspace enables critical alerts to be sent to a + Slack channel. + Learn more
{:else if category == 'SCIM/SAML'}
- Setting up SAML and SCIM allows you to authenticate users using your identity provider. - Learn more + Setting up SAML and SCIM allows you to authenticate users using your identity + provider. + Learn more
{:else if category == 'Debug'}
@@ -310,9 +323,11 @@
{#if $enterpriseLicense} -
- On Enterprise Edition, you must send data to check that usage is in line with the terms of the subscription. You can either enable telemetry or regularly send usage data by clicking the button below. -
+
+ On Enterprise Edition, you must send data to check that usage is in line with the + terms of the subscription. You can either enable telemetry or regularly send usage + data by clicking the button below. +
- {#if jsonStr.length > 100000 || (id && workspace && args && typeof args === 'object' && deepEqual( Object.keys(args), ['reason'] ) && args['reason'] == 'WINDMILL_TOO_BIG')} + {#if jsonStr.length > 100000 || (id && workspace && args && isWindmillTooBigObject(args))}
('FlowEditorContext') // Test - let scriptProgress = undefined; + let scriptProgress = undefined let testJobLoader: TestJobLoader let testIsLoading = false let testJob: Job | undefined = undefined @@ -54,7 +54,7 @@ export async function runTest(args: any) { // Not defined if JobProgressBar not loaded - if (jobProgressReset) jobProgressReset(); + if (jobProgressReset) jobProgressReset() const val = mod.value // let jobId: string | undefined = undefined @@ -75,7 +75,7 @@ script.content, script.language, args, - $flowStore?.tag ?? script.tag + $flowStore?.tag ?? (val.tag_override ? val.tag_override : script.tag) ) } else if (val.type == 'flow') { await testJobLoader?.abstractRun(() => @@ -103,8 +103,6 @@ let forceJson = false - - jobDone()} @@ -158,9 +156,14 @@ /> - {#if scriptProgress} - - {/if} + {#if scriptProgress} + + {/if} {#if testJob != undefined && 'result' in testJob && testJob.result != undefined}
{#if testIsLoading} {#if !scriptProgress} - + {/if} {:else} Test to see the result here diff --git a/frontend/src/lib/components/OAuthSetting.svelte b/frontend/src/lib/components/OAuthSetting.svelte index 632fcc63a1..8277ddf396 100644 --- a/frontend/src/lib/components/OAuthSetting.svelte +++ b/frontend/src/lib/components/OAuthSetting.svelte @@ -66,10 +66,12 @@ > {#if enabled}
- + {#if name != 'slack'} + + {/if}