diff --git a/.github/DockerfileBackendTests b/.github/DockerfileBackendTests index 5792e5753b..e20d023bdc 100644 --- a/.github/DockerfileBackendTests +++ b/.github/DockerfileBackendTests @@ -40,11 +40,11 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER RUN /usr/local/bin/python3 -m pip install pip-tools -COPY --from=oven/bun:1.1.30 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.31 /usr/local/bin/bun /usr/bin/bun ARG TARGETPLATFORM -RUN curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.0/deno-x86_64-unknown-linux-gnu.zip -o deno.zip +RUN curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.2/deno-x86_64-unknown-linux-gnu.zip -o deno.zip # RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.0/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true RUN unzip deno.zip && rm deno.zip && mv deno /usr/bin/deno diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f0928cbd33..6ad794e136 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,6 +26,7 @@ permissions: write-all jobs: build: runs-on: ubicloud + if: (github.event_name != 'issue_comment') || (contains(github.event.comment.body, '/buildimage_all') || contains(github.event.comment.body, '/buildimage_base')) steps: - uses: actions/checkout@v4 with: @@ -87,6 +88,7 @@ jobs: build_ee: runs-on: ubicloud + if: (github.event_name != 'issue_comment') || (contains(github.event.comment.body, '/buildimage_ee') || contains(github.event.comment.body, '/buildimage_nsjail')) || contains(github.event.comment.body, '/buildimage_all') steps: - uses: actions/checkout@v4 with: @@ -436,7 +438,7 @@ jobs: build_ee_nsjail: needs: [build_ee] runs-on: ubicloud - if: github.event_name != 'pull_request' + if: (github.event_name != 'issue_comment') || (github.event_name != 'pull_request') || (contains(github.event.comment.body, '/buildimage_nsjail') || contains(github.event.comment.body, '/buildimage_all')) steps: - uses: actions/checkout@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 218c4a4842..adaa14ec7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,77 @@ # Changelog +## [1.411.1](https://github.com/windmill-labs/windmill/compare/v1.411.0...v1.411.1) (2024-10-22) + + +### Bug Fixes + +* update bun to 1.1.32 ([#4568](https://github.com/windmill-labs/windmill/issues/4568)) ([0586446](https://github.com/windmill-labs/windmill/commit/058644667129f0d79ec147aacdda449142ae0ab9)) + +## [1.411.0](https://github.com/windmill-labs/windmill/compare/v1.410.3...v1.411.0) (2024-10-21) + + +### Features + +* **cli:** encrypt sensitive instance settings ([#4561](https://github.com/windmill-labs/windmill/issues/4561)) ([b8a6a11](https://github.com/windmill-labs/windmill/commit/b8a6a116354b10f5977e54edb365d6711e160538)) + + +### Bug Fixes + +* Do not ignore file resources with json file ext ([#4562](https://github.com/windmill-labs/windmill/issues/4562)) ([2079b2e](https://github.com/windmill-labs/windmill/commit/2079b2e7e19aa2fe327f2ae66d1b5eba988b9b0a)) +* update bun to 1.1.31 and deno to 2.0.2 ([0d90396](https://github.com/windmill-labs/windmill/commit/0d9039641b3348e75599937188c35dd89a000584)) + +## [1.410.3](https://github.com/windmill-labs/windmill/compare/v1.410.2...v1.410.3) (2024-10-20) + + +### Bug Fixes + +* **go-client:** reduce runtime dependencies by bumping oai-codeen to v2.4.1 ([87f5c07](https://github.com/windmill-labs/windmill/commit/87f5c078dd63cd3e0ba3e719c2da7b83f1458e2c)) + +## [1.410.1](https://github.com/windmill-labs/windmill/compare/v1.410.0...v1.410.1) (2024-10-19) + + +### Bug Fixes + +* **cli:** improve wmill init behavior ([26a40d1](https://github.com/windmill-labs/windmill/commit/26a40d19441aa816ee711ce30f3435dddd3542a7)) +* **frontend:** improve display of error handlers ([a92a2fd](https://github.com/windmill-labs/windmill/commit/a92a2fd6fd67c11c04554a90b8ae6d7a0dd9067c)) + +## [1.410.1](https://github.com/windmill-labs/windmill/compare/v1.410.0...v1.410.1) (2024-10-19) + + +### Bug Fixes + +* **cli:** improve wmill init behavior ([26a40d1](https://github.com/windmill-labs/windmill/commit/26a40d19441aa816ee711ce30f3435dddd3542a7)) + +## [1.410.0](https://github.com/windmill-labs/windmill/compare/v1.409.4...v1.410.0) (2024-10-18) + + +### Features + +* **typescript-bun:** support relative imports without the .ts extension ([248fdc2](https://github.com/windmill-labs/windmill/commit/248fdc24a61aca946902b20b9d8187101a9b7bfa)) +* websocket triggers ([#4505](https://github.com/windmill-labs/windmill/issues/4505)) ([8807e99](https://github.com/windmill-labs/windmill/commit/8807e99f06caf3e26c526eefdc83dbc2f7aa93ee)) + + +### Bug Fixes + +* cache js static assets by default ([08595c6](https://github.com/windmill-labs/windmill/commit/08595c6f14a89df1559974e8835e63881a1a9601)) +* **frontend:** add back script lockfile to script details pae ([549b11d](https://github.com/windmill-labs/windmill/commit/549b11dcfb39a60acd6d3899fce51b31149f8594)) +* improve cancelling of jobs on public apps for anonymous users ([d7cf5ea](https://github.com/windmill-labs/windmill/commit/d7cf5ea37db313e78865708bc22e87a1feea9e0d)) + +## [1.409.4](https://github.com/windmill-labs/windmill/compare/v1.409.3...v1.409.4) (2024-10-17) + + +### Bug Fixes + +* fix flow viewer renderer outside of flow details and editor context ([59a1e67](https://github.com/windmill-labs/windmill/commit/59a1e67465cd21ded5539cb5b829bc0d4e7169ed)) + +## [1.409.3](https://github.com/windmill-labs/windmill/compare/v1.409.2...v1.409.3) (2024-10-17) + + +### Bug Fixes + +* do not delete primary schedule of script/flow on redeploy even if schedule wasn't loaded ([c3c2fe4](https://github.com/windmill-labs/windmill/commit/c3c2fe462c52f48795a55025414b16f8d3d98fe0)) +* **nsjail:** improve memory reading when using nsjail ([b7ad19b](https://github.com/windmill-labs/windmill/commit/b7ad19bb75bd885a254ebac778349c7d88af8326)) + ## [1.409.2](https://github.com/windmill-labs/windmill/compare/v1.409.1...v1.409.2) (2024-10-16) diff --git a/Dockerfile b/Dockerfile index 505cf61d50..4afb307c8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -175,9 +175,9 @@ RUN /usr/local/bin/python3 -m pip install pip-tools COPY --from=builder /frontend/build /static_frontend COPY --from=builder /windmill/target/release/windmill ${APP}/windmill -COPY --from=denoland/deno:2.0.0 --chmod=755 /usr/bin/deno /usr/bin/deno +COPY --from=denoland/deno:2.0.2 --chmod=755 /usr/bin/deno /usr/bin/deno -COPY --from=oven/bun:1.1.30 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer diff --git a/backend/.sqlx/query-02424907504848e983bfa89eec343061932dc5b4b17cf13d5cf8d833aedbe6d5.json b/backend/.sqlx/query-02424907504848e983bfa89eec343061932dc5b4b17cf13d5cf8d833aedbe6d5.json new file mode 100644 index 0000000000..c8b5e3086f --- /dev/null +++ b/backend/.sqlx/query-02424907504848e983bfa89eec343061932dc5b4b17cf13d5cf8d833aedbe6d5.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET server_id = $1, last_server_ping = now() WHERE enabled IS TRUE AND workspace_id = $2 AND path = $3 AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') RETURNING true", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "02424907504848e983bfa89eec343061932dc5b4b17cf13d5cf8d833aedbe6d5" +} diff --git a/backend/.sqlx/query-12a86755706ce030a0a9142da78d035d0c7d361240b60005cc864e4345eb0bc7.json b/backend/.sqlx/query-12a86755706ce030a0a9142da78d035d0c7d361240b60005cc864e4345eb0bc7.json new file mode 100644 index 0000000000..dc357c2169 --- /dev/null +++ b/backend/.sqlx/query-12a86755706ce030a0a9142da78d035d0c7d361240b60005cc864e4345eb0bc7.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET enabled = $1, email = $2, edited_by = $3, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE path = $4 AND workspace_id = $5 RETURNING 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Bool", + "Varchar", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "12a86755706ce030a0a9142da78d035d0c7d361240b60005cc864e4345eb0bc7" +} diff --git a/backend/.sqlx/query-9ebb9c16948a695a053068d9a1df0152691271ac33d8c82db47be11746ccbcef.json b/backend/.sqlx/query-2238aaed46031f71b17c1cabcef935a0d3b2ca6d057f446ed9b02d4386e2ddd9.json similarity index 58% rename from backend/.sqlx/query-9ebb9c16948a695a053068d9a1df0152691271ac33d8c82db47be11746ccbcef.json rename to backend/.sqlx/query-2238aaed46031f71b17c1cabcef935a0d3b2ca6d057f446ed9b02d4386e2ddd9.json index ae30d3bd98..aa32886ba8 100644 --- a/backend/.sqlx/query-9ebb9c16948a695a053068d9a1df0152691271ac33d8c82db47be11746ccbcef.json +++ b/backend/.sqlx/query-2238aaed46031f71b17c1cabcef935a0d3b2ca6d057f446ed9b02d4386e2ddd9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1)", + "query": "SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE path = $1 AND workspace_id = $2)", "describe": { "columns": [ { @@ -11,6 +11,7 @@ ], "parameters": { "Left": [ + "Text", "Text" ] }, @@ -18,5 +19,5 @@ null ] }, - "hash": "9ebb9c16948a695a053068d9a1df0152691271ac33d8c82db47be11746ccbcef" + "hash": "2238aaed46031f71b17c1cabcef935a0d3b2ca6d057f446ed9b02d4386e2ddd9" } diff --git a/backend/.sqlx/query-2e6165543e34216dfaedf6e10729f733cff812e62dea1c1bce8cefd3a3979b14.json b/backend/.sqlx/query-2e6165543e34216dfaedf6e10729f733cff812e62dea1c1bce8cefd3a3979b14.json new file mode 100644 index 0000000000..4a15e2d4fa --- /dev/null +++ b/backend/.sqlx/query-2e6165543e34216dfaedf6e10729f733cff812e62dea1c1bce8cefd3a3979b14.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM websocket_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Bool", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "2e6165543e34216dfaedf6e10729f733cff812e62dea1c1bce8cefd3a3979b14" +} diff --git a/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json b/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json new file mode 100644 index 0000000000..2f96d31ecb --- /dev/null +++ b/backend/.sqlx/query-4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775.json @@ -0,0 +1,108 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now()) RETURNING *", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "url", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "filters", + "type_info": "JsonbArray" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Bool", + "JsonbArray", + "Varchar", + "Varchar" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "4e9668a46bad9e82baa51422946d373b18b6577198df7545c94bd19be3446775" +} diff --git a/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json b/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json new file mode 100644 index 0000000000..a95b00b8c8 --- /dev/null +++ b/backend/.sqlx/query-5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7.json @@ -0,0 +1,98 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT *\n FROM websocket_trigger\n WHERE enabled IS TRUE AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "url", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "filters", + "type_info": "JsonbArray" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "5303cb9dd5903aa4791ef8e5e5881a50a832e65c8c9632e2e12cd9c2747f2fc7" +} diff --git a/backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json b/backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json new file mode 100644 index 0000000000..eef46ce41d --- /dev/null +++ b/backend/.sqlx/query-acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, edited_by = $6, email = $7, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE workspace_id = $8 AND path = $9", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Bool", + "JsonbArray", + "Varchar", + "Varchar", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "acbf74cf3302bfcf7615285070d3f8958932bb8a2dda715f1b9152ab44442780" +} diff --git a/backend/.sqlx/query-d54840373df5da9662ed11ed0a605cddac517bdc06dca0a8be071330338e948a.json b/backend/.sqlx/query-d54840373df5da9662ed11ed0a605cddac517bdc06dca0a8be071330338e948a.json new file mode 100644 index 0000000000..5b298f8db3 --- /dev/null +++ b/backend/.sqlx/query-d54840373df5da9662ed11ed0a605cddac517bdc06dca0a8be071330338e948a.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM websocket_trigger WHERE workspace_id = $1 AND path = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d54840373df5da9662ed11ed0a605cddac517bdc06dca0a8be071330338e948a" +} diff --git a/backend/.sqlx/query-db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53.json b/backend/.sqlx/query-db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53.json new file mode 100644 index 0000000000..70f4b91b71 --- /dev/null +++ b/backend/.sqlx/query-db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "websocket_used!", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "http_routes_used!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53" +} diff --git a/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json b/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json new file mode 100644 index 0000000000..2e75b4e74b --- /dev/null +++ b/backend/.sqlx/query-f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021.json @@ -0,0 +1,101 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT *\n FROM websocket_trigger\n WHERE workspace_id = $1 AND path = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "url", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "is_flow", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "edited_by", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "edited_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "extra_perms", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "server_id", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "last_server_ping", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "error", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "filters", + "type_info": "JsonbArray" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false + ] + }, + "hash": "f2baee15e6d1fecd6d2d7b39fda1b50a15ec8bd349f1081af54da5dc2f5e3021" +} diff --git a/backend/.sqlx/query-febd70e6d5304ad912c232d3d8fc3f8ce0133d8a1a9e5ca1124eff9a988dc2d7.json b/backend/.sqlx/query-febd70e6d5304ad912c232d3d8fc3f8ce0133d8a1a9e5ca1124eff9a988dc2d7.json new file mode 100644 index 0000000000..7958f9d2f3 --- /dev/null +++ b/backend/.sqlx/query-febd70e6d5304ad912c232d3d8fc3f8ce0133d8a1a9e5ca1124eff9a988dc2d7.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET last_server_ping = now(), error = $1 WHERE workspace_id = $2 AND path = $3 AND server_id = $4 AND enabled IS TRUE RETURNING 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "febd70e6d5304ad912c232d3d8fc3f8ce0133d8a1a9e5ca1124eff9a988dc2d7" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b8d7319fb4..6e3d854a2a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arc-swap" @@ -441,7 +441,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103db485efc3e41214fe4fda9f3dbeae2eb9082f48fd236e6095627a9422066e" +checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" dependencies = [ "brotli 7.0.0", "bzip2", @@ -588,7 +588,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -636,14 +636,14 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] name = "async-stripe" -version = "0.34.1" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109605d984dd71a9a278e2d43a2831a20e80f25c3bcc25174096d12352cfc469" +checksum = "58d670cf4d47a1b8ffef54286a5625382e360a34ee76902fd93ad8c7032a0c30" dependencies = [ "chrono", "futures-util", @@ -678,7 +678,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1158,9 +1158,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bb8" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10cf871f3ff2ce56432fddc2615ac7acc3aa22ca321f8fea800846fbb32f188" +checksum = "d89aabfae550a5c44b43ab941844ffcd2e993cb6900b342debf59e9ea74acdb8" dependencies = [ "async-trait", "futures-util", @@ -1212,13 +1212,13 @@ dependencies = [ "lazy_static", "lazycell", "log", - "prettyplease 0.2.22", + "prettyplease 0.2.24", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.79", + "syn 2.0.82", "which 4.4.2", ] @@ -1237,7 +1237,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1381,7 +1381,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "syn_derive", ] @@ -1484,7 +1484,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1495,9 +1495,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytes-utils" @@ -1599,9 +1599,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.30" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -1744,7 +1744,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2124,7 +2124,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2172,7 +2172,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2194,7 +2194,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2233,7 +2233,7 @@ dependencies = [ "arrow-array", "arrow-ipc", "arrow-schema", - "async-compression 0.4.16", + "async-compression 0.4.17", "async-trait", "bytes", "bzip2", @@ -2715,7 +2715,7 @@ dependencies = [ "quote", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.79", + "syn 2.0.82", "thiserror", ] @@ -2907,7 +2907,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3004,7 +3004,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3160,7 +3160,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3180,7 +3180,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3336,9 +3336,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -3395,7 +3395,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3427,7 +3427,7 @@ checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3439,7 +3439,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3451,7 +3451,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3575,7 +3575,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3828,7 +3828,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -4463,7 +4463,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -4672,9 +4672,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libgit2-sys" @@ -5113,7 +5113,7 @@ checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -5152,7 +5152,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "termcolor", "thiserror", ] @@ -5562,7 +5562,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -5839,7 +5839,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -5880,7 +5880,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -6057,12 +6057,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d" dependencies = [ "proc-macro2", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -6115,7 +6115,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -6127,14 +6127,14 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -6188,7 +6188,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "syn 2.0.79", + "syn 2.0.82", "thiserror", "typify", "unicode-ident", @@ -6208,7 +6208,7 @@ dependencies = [ "serde_json", "serde_tokenstream", "serde_yaml", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -6251,7 +6251,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -6728,7 +6728,7 @@ version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ - "async-compression 0.4.16", + "async-compression 0.4.17", "base64 0.22.1", "bytes", "encoding_rs", @@ -6934,7 +6934,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.79", + "syn 2.0.82", "walkdir", ] @@ -7314,7 +7314,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -7414,9 +7414,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.212" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "ccd4055b7e3937a5c2595e974f5bf1715a23919a595a04b5ad959bdbbb61ab04" dependencies = [ "serde_derive", ] @@ -7455,13 +7455,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.212" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "726adf8349784fb68a42e6466f49362ae039d9c5333cc6eb131f4d6f94bb9126" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -7472,14 +7472,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "indexmap 2.6.0", "itoa", @@ -7547,7 +7547,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -7602,7 +7602,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -7971,7 +7971,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8042,7 +8042,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8065,7 +8065,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.79", + "syn 2.0.82", "tempfile", "tokio", "url", @@ -8217,7 +8217,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8271,7 +8271,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8284,7 +8284,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8392,7 +8392,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8441,7 +8441,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8526,7 +8526,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8633,7 +8633,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8644,7 +8644,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8667,7 +8667,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8683,9 +8683,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -8701,7 +8701,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8727,7 +8727,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -8977,22 +8977,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -9217,7 +9217,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -9315,6 +9315,20 @@ dependencies = [ "xattr", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -9472,7 +9486,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ - "async-compression 0.4.16", + "async-compression 0.4.17", "bitflags 2.6.0", "bytes", "futures-core", @@ -9530,7 +9544,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -9702,6 +9716,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -9748,7 +9781,7 @@ dependencies = [ "regress", "schemars", "serde_json", - "syn 2.0.79", + "syn 2.0.82", "thiserror", "unicode-ident", ] @@ -9765,7 +9798,7 @@ dependencies = [ "serde", "serde_json", "serde_tokenstream", - "syn 2.0.79", + "syn 2.0.82", "typify-impl", ] @@ -9847,12 +9880,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" @@ -9868,9 +9898,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" [[package]] name = "unicode-id-start" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97e2a3c5fc9de285c0e805d98eba666adb4b2d9e1049ce44821ff7707cc34e91" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" @@ -10015,6 +10045,12 @@ dependencies = [ "url", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-ranges" version = "1.0.5" @@ -10157,7 +10193,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "wasm-bindgen-shared", ] @@ -10191,7 +10227,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10224,7 +10260,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -10358,7 +10394,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "axum", @@ -10399,7 +10435,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "argon2", @@ -10461,6 +10497,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-tar", + "tokio-tungstenite", "tokio-util", "tower 0.5.1", "tower-cookies", @@ -10483,7 +10520,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.409.2" +version = "1.411.1" dependencies = [ "base64 0.21.7", "chrono", @@ -10501,7 +10538,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.409.2" +version = "1.411.1" dependencies = [ "chrono", "serde", @@ -10514,7 +10551,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "async-stream", @@ -10560,7 +10597,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.409.2" +version = "1.411.1" dependencies = [ "regex", "rsmq_async", @@ -10575,7 +10612,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "bytes", @@ -10597,19 +10634,19 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.405.5" +version = "1.411.1" dependencies = [ - "itertools 0.10.5", + "itertools 0.13.0", "lazy_static", "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] name = "windmill-parser" -version = "1.409.2" +version = "1.411.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -10618,7 +10655,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "lazy_static", @@ -10630,7 +10667,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "gosyn", @@ -10642,7 +10679,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "lazy_static", @@ -10654,7 +10691,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10665,7 +10702,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10676,7 +10713,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "async-recursion", @@ -10694,7 +10731,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10704,14 +10741,14 @@ dependencies = [ "quote", "regex", "serde_json", - "syn 2.0.79", + "syn 2.0.82", "toml 0.7.8", "windmill-parser", ] [[package]] name = "windmill-parser-sql" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "lazy_static", @@ -10723,7 +10760,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "lazy_static", @@ -10741,7 +10778,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10762,7 +10799,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "serde_json", @@ -10772,7 +10809,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "async-recursion", @@ -10805,7 +10842,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.409.2" +version = "1.411.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -10815,7 +10852,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.409.2" +version = "1.411.1" dependencies = [ "anyhow", "async-recursion", @@ -11181,7 +11218,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "synstructure", ] @@ -11203,7 +11240,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -11223,7 +11260,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "synstructure", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c8b68ac1b1..11a097d2b7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.409.2" +version = "1.411.1" authors.workspace = true edition.workspace = true @@ -28,7 +28,7 @@ members = [ ] [workspace.package] -version = "1.409.2" +version = "1.411.1" authors = ["Ruben Fiszel "] edition = "2021" @@ -219,7 +219,7 @@ serde_derive = "1.0.147" const_format = { version = "0.2", features = ["rust_1_64", "rust_1_51"] } dyn-iter = "0.2.0" rsa = "0.7.2" -async-stripe = { version = "0.34.1", features = [ +async-stripe = { version = "0.39.1", features = [ "runtime-tokio-hyper", "checkout", "billing", @@ -291,3 +291,4 @@ syn = { version = "2.0.74", features = ["full"] } quote = "1.0.36" regex-lite = "0.1.6" yaml-rust = "0.4.5" +tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } \ No newline at end of file diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 1472a7591a..0b60c124b1 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -0428068e4fbbd1380a4d8bbaab5c8e7955decdb8 +d61c163e0a311ecd86d1398aff883eaea8d0b09a diff --git a/backend/migrations/20241002163207_add_websocket_triggers.down.sql b/backend/migrations/20241002163207_add_websocket_triggers.down.sql new file mode 100644 index 0000000000..bb579574a8 --- /dev/null +++ b/backend/migrations/20241002163207_add_websocket_triggers.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +DROP TABLE websocket_trigger; \ No newline at end of file diff --git a/backend/migrations/20241002163207_add_websocket_triggers.up.sql b/backend/migrations/20241002163207_add_websocket_triggers.up.sql new file mode 100644 index 0000000000..4d6b44cb8f --- /dev/null +++ b/backend/migrations/20241002163207_add_websocket_triggers.up.sql @@ -0,0 +1,67 @@ +-- Add up migration script here + +CREATE TABLE websocket_trigger ( + path VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + script_path VARCHAR(255) NOT NULL, + is_flow BOOLEAN NOT NULL, + workspace_id VARCHAR(50) NOT NULL, + edited_by VARCHAR(50) NOT NULL, + email VARCHAR(255) NOT NULL, + edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + extra_perms JSONB NOT NULL DEFAULT '{}', + server_id VARCHAR(50) NULL, + last_server_ping TIMESTAMPTZ NULL, + error TEXT NULL, + enabled BOOLEAN NOT NULL, + filters JSONB[] NOT NULL DEFAULT '{}', + PRIMARY KEY (path, workspace_id) +); + +GRANT ALL ON websocket_trigger TO windmill_user; +GRANT ALL ON websocket_trigger TO windmill_admin; + +ALTER TABLE websocket_trigger ENABLE ROW LEVEL SECURITY; + +CREATE POLICY admin_policy ON websocket_trigger FOR ALL TO windmill_admin USING (true); + +CREATE POLICY see_folder_extra_perms_user_select ON websocket_trigger FOR SELECT TO windmill_user +USING (SPLIT_PART(websocket_trigger.path, '/', 1) = 'f' AND SPLIT_PART(websocket_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_insert ON websocket_trigger FOR INSERT TO windmill_user +WITH CHECK (SPLIT_PART(websocket_trigger.path, '/', 1) = 'f' AND SPLIT_PART(websocket_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_update ON websocket_trigger FOR UPDATE TO windmill_user +USING (SPLIT_PART(websocket_trigger.path, '/', 1) = 'f' AND SPLIT_PART(websocket_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); +CREATE POLICY see_folder_extra_perms_user_delete ON websocket_trigger FOR DELETE TO windmill_user +USING (SPLIT_PART(websocket_trigger.path, '/', 1) = 'f' AND SPLIT_PART(websocket_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); + +CREATE POLICY see_own ON websocket_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(websocket_trigger.path, '/', 1) = 'u' AND SPLIT_PART(websocket_trigger.path, '/', 2) = current_setting('session.user')); +CREATE POLICY see_member ON websocket_trigger FOR ALL TO windmill_user +USING (SPLIT_PART(websocket_trigger.path, '/', 1) = 'g' AND SPLIT_PART(websocket_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[])); + +CREATE POLICY see_extra_perms_user_select ON websocket_trigger FOR SELECT TO windmill_user +USING (extra_perms ? CONCAT('u/', current_setting('session.user'))); +CREATE POLICY see_extra_perms_user_insert ON websocket_trigger FOR INSERT TO windmill_user +WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_update ON websocket_trigger FOR UPDATE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); +CREATE POLICY see_extra_perms_user_delete ON websocket_trigger FOR DELETE TO windmill_user +USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean); + +CREATE POLICY see_extra_perms_groups_select ON websocket_trigger FOR SELECT TO windmill_user +USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]); +CREATE POLICY see_extra_perms_groups_insert ON websocket_trigger FOR INSERT TO windmill_user +WITH CHECK (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_update ON websocket_trigger FOR UPDATE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); +CREATE POLICY see_extra_perms_groups_delete ON websocket_trigger FOR DELETE TO windmill_user +USING (exists( + SELECT key, value FROM jsonb_each_text(extra_perms) + WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) + AND value::boolean)); \ No newline at end of file diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index e4b6b5d71b..b7c4a41aed 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -95,6 +95,7 @@ openidconnect = { workspace = true, optional = true} url = { workspace = true, optional = true} jsonwebtoken = { workspace = true } matchit.workspace = true +tokio-tungstenite.workspace = true pin-project.workspace = true http.workspace = true diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 1744b48526..969bdb0cac 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.409.2 + version: 1.411.1 title: Windmill API contact: @@ -2162,6 +2162,30 @@ paths: schema: type: number + /w/{workspace}/workspaces/used_triggers: + get: + summary: get used triggers + operationId: getUsedTriggers + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: status + content: + application/json: + schema: + type: object + properties: + http_routes_used: + type: boolean + websocket_used: + type: boolean + required: + - http_routes_used + - websocket_used + /w/{workspace}/users/list: get: summary: list users @@ -4092,6 +4116,26 @@ paths: items: $ref: "#/components/schemas/ScriptHistory" + /w/{workspace}/scripts/get_latest_version/{path}: + get: + summary: get scripts's latest version (hash) + operationId: getScriptLatestVersion + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + tags: + - script + responses: + "200": + description: Script version/hash + content: + application/json: + + required: false + + schema: + $ref: "#/components/schemas/ScriptHistory" + /w/{workspace}/scripts/history_update/h/{hash}/p/{path}: post: summary: update history of a script @@ -4580,6 +4624,25 @@ paths: items: $ref: "#/components/schemas/FlowVersion" + /w/{workspace}/flows/get_latest_version/{path}: + get: + summary: get flow's latest version + operationId: getFlowLatestVersion + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + tags: + - flow + responses: + "200": + description: Flow version + content: + application/json: + required: false + + schema: + $ref: "#/components/schemas/FlowVersion" + /w/{workspace}/flows/get/v/{version}/p/{path}: get: summary: get flow version @@ -5134,6 +5197,24 @@ paths: items: $ref: "#/components/schemas/AppHistory" + /w/{workspace}/apps/get_latest_version/{path}: + get: + summary: get apps's latest version + operationId: getAppLatestVersion + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + tags: + - app + responses: + "200": + description: App version + content: + application/json: + required: false + schema: + $ref: "#/components/schemas/AppHistory" + /w/{workspace}/apps/history_update/a/{id}/v/{version}: post: summary: update app history @@ -7152,22 +7233,170 @@ paths: schema: type: boolean - /w/{workspace}/http_triggers/used: - get: - summary: whether http triggers are used - operationId: used + /w/{workspace}/websocket_triggers/create: + post: + summary: create websocket trigger + operationId: createWebsocketTrigger tags: - - http_trigger + - websocket_trigger parameters: - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: new websocket trigger + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewWebsocketTrigger" + responses: + "201": + description: websocket trigger created + content: + text/plain: + schema: + type: string + + /w/{workspace}/websocket_triggers/update/{path}: + post: + summary: update websocket trigger + operationId: updateWebsocketTrigger + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: updated trigger + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EditWebsocketTrigger" responses: "200": - description: whether http triggers are used + description: websocket trigger updated + content: + text/plain: + schema: + type: string + + /w/{workspace}/websocket_triggers/delete/{path}: + delete: + summary: delete websocket trigger + operationId: deleteWebsocketTrigger + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: websocket trigger deleted + content: + text/plain: + schema: + type: string + + /w/{workspace}/websocket_triggers/get/{path}: + get: + summary: get websocket trigger + operationId: getWebsocketTrigger + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: websocket trigger deleted + content: + application/json: + schema: + $ref: "#/components/schemas/WebsocketTrigger" + + + /w/{workspace}/websocket_triggers/list: + get: + summary: list websocket triggers + operationId: listWebsocketTriggers + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + required: true + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" + - name: path + description: filter by path + in: query + schema: + type: string + - name: is_flow + in: query + schema: + type: boolean + - name: path_start + in: query + schema: + type: string + responses: + "200": + description: websocket trigger list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/WebsocketTrigger" + + + /w/{workspace}/websocket_triggers/exists/{path}: + get: + summary: does websocket trigger exists + operationId: existsWebsocketTrigger + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: websocket trigger exists content: application/json: schema: type: boolean + /w/{workspace}/websocket_triggers/setenabled/{path}: + post: + summary: set enabled websocket trigger + operationId: setWebsocketTriggerEnabled + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + requestBody: + description: updated websocket trigger enable + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + required: + - enabled + responses: + "200": + description: websocket trigger enabled set + content: + text/plain: + schema: + type: string + + /groups/list: get: summary: list instance groups @@ -7995,6 +8224,7 @@ paths: app, raw_app, http_trigger, + websocket_trigger, ] responses: "200": @@ -8032,6 +8262,7 @@ paths: app, raw_app, http_trigger, + websocket_trigger, ] requestBody: description: acl to add @@ -8080,6 +8311,7 @@ paths: app, raw_app, http_trigger, + websocket_trigger, ] requestBody: description: acl to add @@ -10503,6 +10735,7 @@ components: - "users.delete" - "users.update" - "users.login" + - "users.login_failure" - "users.logout" - "users.accept_invite" - "users.decline_invite" @@ -10514,6 +10747,7 @@ components: - "users.impersonate" - "users.leave_workspace" - "oauth.login" + - "oauth.login_failure" - "oauth.signup" - "variables.create" - "variables.delete" @@ -11213,6 +11447,128 @@ components: type: number email_count: type: number + websocket_count: + type: number + + WebsocketTrigger: + type: object + properties: + path: + type: string + edited_by: + type: string + edited_at: + type: string + format: date-time + script_path: + type: string + url: + type: string + is_flow: + type: boolean + extra_perms: + type: object + additionalProperties: + type: boolean + email: + type: string + workspace_id: + type: string + server_id: + type: string + last_server_ping: + type: string + format: date-time + error: + type: string + enabled: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + + required: + - path + - edited_by + - edited_at + - script_path + - url + - extra_perms + - is_flow + - email + - workspace_id + - enabled + - filters + + NewWebsocketTrigger: + type: object + properties: + path: + type: string + script_path: + type: string + is_flow: + type: boolean + url: + type: string + enabled: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + + required: + - path + - script_path + - url + - is_flow + - filters + + EditWebsocketTrigger: + type: object + properties: + url: + type: string + path: + type: string + script_path: + type: string + is_flow: + type: boolean + filters: + type: array + items: + type: object + properties: + key: + type: string + value: {} + required: + - key + - value + + required: + - path + - script_path + - url + - is_flow + - filters Group: type: object diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 7bed72c50e..cd65136a2a 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -62,6 +62,7 @@ pub fn workspaced_service() -> Router { .route("/delete/*path", delete(delete_app)) .route("/create", post(create_app)) .route("/history/p/*path", get(get_app_history)) + .route("/get_latest_version/*path", get(get_latest_version)) .route("/history_update/a/:id/v/:version", post(update_app_history)) } @@ -427,6 +428,38 @@ async fn get_app_history( return Ok(Json(result)); } +async fn get_latest_version( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + +) -> JsonResult> { + + let mut tx = user_db.begin(&authed).await?; + let row = sqlx::query!( + "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg + FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version + WHERE a.workspace_id = $1 AND a.path = $2 + ORDER BY created_at DESC", + w_id, + path.to_path(), + ).fetch_optional(&mut *tx).await?; + tx.commit().await?; + + if let Some(row) = row { + let result = AppHistory { + app_id: row.app_id, + version: row.version_id, + deployment_msg: row.deployment_msg, + }; + + return Ok(Json(Some(result))); + } else { + return Ok(Json(None)); + } + +} + async fn update_app_history( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index de183ae6eb..3644e04d30 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -63,6 +63,7 @@ pub fn workspaced_service() -> Router { .route("/exists/*path", get(exists_flow_by_path)) .route("/list_paths", get(list_paths)) .route("/history/p/*path", get(get_flow_history)) + .route("/get_latest_version/*path", get(get_latest_version)) .route( "/history_update/v/:version/p/*path", post(update_flow_history), @@ -538,6 +539,31 @@ async fn get_flow_history( Ok(Json(flows)) } +async fn get_latest_version( + authed: ApiAuthed, + 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?; + + let version = sqlx::query_as!( + FlowVersion, + "SELECT flow_version.id, flow_version.created_at, deployment_metadata.deployment_msg FROM flow_version + LEFT JOIN deployment_metadata ON flow_version.id = deployment_metadata.flow_version + WHERE flow_version.path = $1 AND flow_version.workspace_id = $2 + ORDER BY flow_version.created_at DESC", + path, + w_id + ) + .fetch_optional(&mut *tx) + .await?; + tx.commit().await?; + + Ok(Json(version)) +} + async fn get_flow_version( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 9e30743fbc..61757cb164 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -12,10 +12,8 @@ use std::collections::HashMap; use tower_http::cors::CorsLayer; use windmill_audit::{audit_ee::audit_log, ActionKind}; use windmill_common::{ - auth::fetch_authed_from_permissioned_as, db::UserDB, error::{self, JsonResult}, - users::username_to_permissioned_as, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, }; @@ -27,7 +25,7 @@ use crate::{ run_flow_by_path_inner, run_script_by_path_inner, run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, }, - users::OptAuthed, + users::{fetch_api_authed, OptAuthed}, }; lazy_static::lazy_static! { @@ -66,7 +64,6 @@ pub fn workspaced_service() -> Router { .route("/update/*path", post(update_trigger)) .route("/delete/*path", delete(delete_trigger)) .route("/exists/*path", get(exists_trigger)) - .route("/used", get(used)) .route("/route_exists", post(exists_route)) } @@ -346,17 +343,6 @@ async fn delete_trigger( Ok(format!("HTTP trigger {path} deleted")) } -async fn used(Extension(db): Extension, Path(w_id): Path) -> JsonResult { - let used = sqlx::query_scalar!( - r#"SELECT EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1)"#, - w_id, - ) - .fetch_one(&db) - .await? - .unwrap_or(false); - Ok(Json(used)) -} - async fn exists_trigger( Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, @@ -421,28 +407,6 @@ struct TriggerRoute { http_method: HttpMethod, } -async fn fetch_api_authed( - username: String, - email: String, - w_id: &str, - db: &DB, - username_override: String, -) -> error::Result { - let permissioned_as = username_to_permissioned_as(username.as_str()); - let authed = - fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?; - Ok(ApiAuthed { - username: username, - email: email, - is_admin: authed.is_admin, - is_operator: authed.is_operator, - groups: authed.groups, - folders: authed.folders, - scopes: authed.scopes, - username_override: Some(username_override), - }) -} - async fn get_http_route_trigger( route_path: &str, opt_authed: Option, diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 817a6f0d02..53cd958c6f 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -70,7 +70,10 @@ use windmill_common::{ oauth2::HmacSha256, scripts::{ScriptHash, ScriptLang}, users::username_to_permissioned_as, - utils::{not_found_if_none, now_from_db, paginate, paginate_without_limits, require_admin, Pagination, StripPath}, + utils::{ + not_found_if_none, now_from_db, paginate, paginate_without_limits, require_admin, + Pagination, StripPath, + }, }; #[cfg(all(feature = "enterprise", feature = "parquet"))] @@ -4751,7 +4754,6 @@ async fn get_job_update( &w_id, job_id, "progress_perc" - ) .fetch_optional(&db) .await?.and_then(|inner| inner) diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 78108a61a4..45a0ea7196 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -39,7 +39,7 @@ use tower_http::{ trace::TraceLayer, }; use windmill_common::db::UserDB; -use windmill_common::worker::ALL_TAGS; +use windmill_common::worker::{ALL_TAGS, CLOUD_HOSTED}; use windmill_common::{BASE_URL, INSTANCE_NAME}; use crate::scim_ee::has_scim_token; @@ -87,6 +87,7 @@ mod users; mod utils; mod variables; mod webhook_util; +mod websocket_triggers; mod workers; mod workspaces; @@ -225,7 +226,7 @@ pub async fn run_server( db: db.clone(), user_db: user_db, auth_cache: auth_cache.clone(), - rsmq: rsmq, + rsmq: rsmq.clone(), base_internal_url: base_internal_url.clone(), }); if let Err(err) = smtp_server.start_listener_thread(addr).await { @@ -245,6 +246,11 @@ pub async fn run_server( } }; + if !*CLOUD_HOSTED { + let ws_killpill_rx = rx.resubscribe(); + websocket_triggers::start_websockets(db.clone(), rsmq, ws_killpill_rx).await; + } + // build our application with a route let app = Router::new() .nest( @@ -285,7 +291,11 @@ pub async fn run_server( .nest("/variables", variables::workspaced_service()) .nest("/workspaces", workspaces::workspaced_service()) .nest("/oidc", oidc_ee::workspaced_service()) - .nest("/http_triggers", http_triggers::workspaced_service()), + .nest("/http_triggers", http_triggers::workspaced_service()) + .nest( + "/websocket_triggers", + websocket_triggers::workspaced_service(), + ), ) .nest("/workspaces", workspaces::global_service()) .nest( diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 9b91c6b5fb..56b991713f 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -152,6 +152,7 @@ pub fn workspaced_service() -> Router { post(toggle_workspace_error_handler), ) .route("/history/p/*path", get(get_script_history)) + .route("/get_latest_version/*path", get(get_latest_version)) .route( "/history_update/h/:hash/p/*path", post(update_script_history), @@ -948,6 +949,38 @@ async fn get_script_history( return Ok(Json(result)); } +async fn get_latest_version( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult> { + let mut tx = user_db.begin(&authed).await?; + let row_o = sqlx::query!( + + "SELECT s.hash as hash, dm.deployment_msg as deployment_msg + FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash + WHERE s.workspace_id = $1 AND s.path = $2 + ORDER by created_at DESC", + w_id, + path.to_path(), + ) + + .fetch_optional(&mut *tx) + .await?; + tx.commit().await?; + + if let Some(row) = row_o { + let result = ScriptHistory { + script_hash: ScriptHash(row.hash), + deployment_msg: row.deployment_msg, // + }; + return Ok(Json(Some(result))); + } else { + return Ok(Json(None)); + } + +} + async fn update_script_history( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index aa9870907e..783732b1c0 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -48,7 +48,10 @@ fn serve_path(path: &str) -> Response { let mut res = Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - if mime.as_ref() == mime::APPLICATION_JAVASCRIPT || path.ends_with(".wasm") { + if mime.as_ref() == mime::APPLICATION_JAVASCRIPT + || mime.as_ref() == mime::TEXT_JAVASCRIPT + || path.ends_with(".wasm") + { res = res.header(header::CACHE_CONTROL, "max-age=31536000"); } else if (mime.type_(), mime.subtype()) == (mime::TEXT, mime::CSS) { res = res.header(header::CACHE_CONTROL, "max-age=31536000"); diff --git a/backend/windmill-api/src/triggers.rs b/backend/windmill-api/src/triggers.rs index dbc5306027..337a20364b 100644 --- a/backend/windmill-api/src/triggers.rs +++ b/backend/windmill-api/src/triggers.rs @@ -17,6 +17,7 @@ pub struct TriggersCount { http_routes_count: i64, webhook_count: i64, email_count: i64, + websocket_count: i64, } pub(crate) async fn get_triggers_count_internal( db: &DB, @@ -53,6 +54,16 @@ pub(crate) async fn get_triggers_count_internal( .await? .unwrap_or(0); + let websocket_count = sqlx::query_scalar!( + "SELECT COUNT(*) FROM websocket_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3", + path, + is_flow, + w_id + ) + .fetch_one(db) + .await? + .unwrap_or(0); + let webhook_count = (if is_flow { sqlx::query_scalar!( "SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]", @@ -93,6 +104,7 @@ pub(crate) async fn get_triggers_count_internal( http_routes_count, webhook_count, email_count, + websocket_count, })) } diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs new file mode 100644 index 0000000000..b94ed85461 --- /dev/null +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -0,0 +1,684 @@ +use axum::{ + extract::{Path, Query}, + routing::{delete, get, post}, + Extension, Json, Router, +}; +use futures::StreamExt; +use http::StatusCode; +use itertools::Itertools; +use rand::seq::SliceRandom; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use serde_json::Value; +use sql_builder::{bind::Bind, SqlBuilder}; +use sqlx::prelude::FromRow; +use std::{collections::HashMap, fmt}; +use tokio_tungstenite::connect_async; +use windmill_audit::{audit_ee::audit_log, ActionKind}; +use windmill_common::{ + db::UserDB, + error::{self, JsonResult}, + utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, + worker::{to_raw_value, CLOUD_HOSTED}, + INSTANCE_NAME, +}; +use windmill_queue::PushArgsOwned; + +use crate::{ + db::{ApiAuthed, DB}, + jobs::{ + run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, + }, + users::fetch_api_authed, +}; + +pub fn workspaced_service() -> Router { + Router::new() + .route("/create", post(create_websocket_trigger)) + .route("/list", get(list_websocket_triggers)) + .route("/get/*path", get(get_websocket_trigger)) + .route("/update/*path", post(update_websocket_trigger)) + .route("/delete/*path", delete(delete_websocket_trigger)) + .route("/exists/*path", get(exists_websocket_trigger)) + .route("/setenabled/*path", post(set_enabled)) +} + +#[derive(Deserialize)] +struct NewWebsocketTrigger { + path: String, + url: String, + script_path: String, + is_flow: bool, + enabled: Option, + filters: Vec, +} + +#[derive(FromRow, Serialize, Clone)] +pub struct WebsocketTrigger { + workspace_id: String, + path: String, + url: String, + script_path: String, + is_flow: bool, + edited_by: String, + email: String, + edited_at: chrono::DateTime, + server_id: Option, + last_server_ping: Option>, + extra_perms: serde_json::Value, + error: Option, + enabled: bool, + filters: Vec, +} + +#[derive(Deserialize)] +struct EditWebsocketTrigger { + path: String, + url: String, + script_path: String, + is_flow: bool, + filters: Vec, +} + +#[derive(Deserialize)] +pub struct ListWebsocketTriggerQuery { + pub page: Option, + pub per_page: Option, + pub path: Option, + pub is_flow: Option, + pub path_start: Option, +} + +async fn list_websocket_triggers( + authed: ApiAuthed, + Extension(user_db): Extension, + Path(w_id): Path, + Query(lst): Query, +) -> error::JsonResult> { + let mut tx = user_db.begin(&authed).await?; + let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); + let mut sqlb = SqlBuilder::select_from("websocket_trigger") + .field("*") + .order_by("edited_at", true) + .and_where("workspace_id = ?".bind(&w_id)) + .offset(offset) + .limit(per_page) + .clone(); + if let Some(path) = lst.path { + sqlb.and_where_eq("script_path", "?".bind(&path)); + } + if let Some(is_flow) = lst.is_flow { + sqlb.and_where_eq("is_flow", "?".bind(&is_flow)); + } + if let Some(path_start) = &lst.path_start { + sqlb.and_where_like_left("path", path_start); + } + let sql = sqlb + .sql() + .map_err(|e| error::Error::InternalErr(e.to_string()))?; + let rows = sqlx::query_as::<_, WebsocketTrigger>(&sql) + .fetch_all(&mut *tx) + .await?; + tx.commit().await?; + + Ok(Json(rows)) +} + +async fn get_websocket_trigger( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> error::JsonResult { + let mut tx = user_db.begin(&authed).await?; + let path = path.to_path(); + let trigger = sqlx::query_as!( + WebsocketTrigger, + r#"SELECT * + FROM websocket_trigger + WHERE workspace_id = $1 AND path = $2"#, + w_id, + path, + ) + .fetch_optional(&mut *tx) + .await?; + tx.commit().await?; + + let trigger = not_found_if_none(trigger, "Trigger", path)?; + + Ok(Json(trigger)) +} + +async fn create_websocket_trigger( + authed: ApiAuthed, + Extension(user_db): Extension, + Path(w_id): Path, + Json(ct): Json, +) -> error::Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + return Err(error::Error::BadRequest( + "Websocket triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), + )); + } + require_admin(authed.is_admin, &authed.username)?; + + let mut tx = user_db.begin(&authed).await?; + sqlx::query_as!( + WebsocketTrigger, + "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now()) RETURNING *", + w_id, + ct.path, + ct.url, + ct.script_path, + ct.is_flow, + ct.enabled.unwrap_or(true), + &ct.filters, + &authed.username, + &authed.email + ) + .fetch_one(&mut *tx).await?; + + audit_log( + &mut *tx, + &authed, + "websocket_triggers.create", + ActionKind::Create, + &w_id, + Some(ct.path.as_str()), + None, + ) + .await?; + + tx.commit().await?; + + Ok((StatusCode::CREATED, format!("{}", ct.path))) +} + +async fn update_websocket_trigger( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Json(ct): Json, +) -> error::Result { + let path = path.to_path(); + let mut tx = user_db.begin(&authed).await?; + + // important to update server_id, last_server_ping and error to NULL to stop current websocket listener + sqlx::query!( + "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, edited_by = $6, email = $7, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL + WHERE workspace_id = $8 AND path = $9", + ct.url, + ct.script_path, + ct.path, + ct.is_flow, + &ct.filters, + &authed.username, + &authed.email, + w_id, + path, + ) + .execute(&mut *tx).await?; + + audit_log( + &mut *tx, + &authed, + "websocket_triggers.update", + ActionKind::Create, + &w_id, + Some(path), + None, + ) + .await?; + + tx.commit().await?; + + Ok(path.to_string()) +} + +#[derive(Deserialize)] +pub struct SetEnabled { + pub enabled: bool, +} + +pub async fn set_enabled( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Json(payload): Json, +) -> error::Result { + let mut tx = user_db.begin(&authed).await?; + let path = path.to_path(); + + // important to set server_id, last_server_ping and error to NULL to stop current websocket listener + let one_o = sqlx::query_scalar!( + "UPDATE websocket_trigger SET enabled = $1, email = $2, edited_by = $3, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL + WHERE path = $4 AND workspace_id = $5 RETURNING 1", + payload.enabled, + &authed.email, + &authed.username, + path, + w_id, + ).fetch_optional(&mut *tx).await?; + + not_found_if_none(one_o.flatten(), "Websocket trigger", path)?; + + audit_log( + &mut *tx, + &authed, + "websocket_triggers.setenabled", + ActionKind::Update, + &w_id, + Some(path), + Some([("enabled", payload.enabled.to_string().as_ref())].into()), + ) + .await?; + + tx.commit().await?; + + Ok(format!( + "succesfully updated websocket trigger at path {} to status {}", + path, payload.enabled + )) +} + +async fn delete_websocket_trigger( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> error::Result { + require_admin(authed.is_admin, &authed.username)?; + let path = path.to_path(); + let mut tx = user_db.begin(&authed).await?; + sqlx::query!( + "DELETE FROM websocket_trigger WHERE workspace_id = $1 AND path = $2", + w_id, + path, + ) + .execute(&mut *tx) + .await?; + + audit_log( + &mut *tx, + &authed, + "websocket_triggers.delete", + ActionKind::Delete, + &w_id, + Some(path), + None, + ) + .await?; + + tx.commit().await?; + + Ok(format!("Websocket trigger {path} deleted")) +} + +async fn exists_websocket_trigger( + Extension(db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult { + let path = path.to_path(); + let exists = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE path = $1 AND workspace_id = $2)", + path, + w_id, + ) + .fetch_one(&db) + .await? + .unwrap_or(false); + Ok(Json(exists)) +} + +async fn listen_to_unlistened_websockets( + db: &DB, + rsmq: &Option, + killpill_rx: &tokio::sync::broadcast::Receiver<()>, +) -> () { + match sqlx::query_as!( + WebsocketTrigger, + r#"SELECT * + FROM websocket_trigger + WHERE enabled IS TRUE AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')"# + ) + .fetch_all(db) + .await + { + Ok(mut triggers) => { + triggers.shuffle(&mut rand::thread_rng()); + for trigger in triggers { + maybe_listen_to_websocket(trigger, db.clone(), rsmq.clone(), killpill_rx.resubscribe()).await; + } + } + Err(err) => { + tracing::error!("Error fetching websocket triggers: {:?}", err); + } + }; +} + +pub async fn start_websockets( + db: DB, + rsmq: Option, + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, +) -> () { + tokio::spawn(async move { + listen_to_unlistened_websockets(&db, &rsmq, &killpill_rx).await; + loop { + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + } + _ = tokio::time::sleep(tokio::time::Duration::from_secs(15)) => { + listen_to_unlistened_websockets(&db, &rsmq, &killpill_rx).await; + } + } + } + }); +} + +async fn maybe_listen_to_websocket( + ws_trigger: WebsocketTrigger, + db: DB, + rsmq: Option, + killpill_rx: tokio::sync::broadcast::Receiver<()>, +) -> () { + match sqlx::query_scalar!( + "UPDATE websocket_trigger SET server_id = $1, last_server_ping = now() WHERE enabled IS TRUE AND workspace_id = $2 AND path = $3 AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') RETURNING true", + *INSTANCE_NAME, + ws_trigger.workspace_id, + ws_trigger.path, + ).fetch_optional(&db).await { + Ok(has_lock) => { + if has_lock.flatten().unwrap_or(false) { + tokio::spawn(listen_to_websocket(ws_trigger, db, rsmq, killpill_rx)); + } else { + tracing::info!("Websocket {} already being listened to", ws_trigger.url); + } + }, + Err(err) => { + tracing::error!("Error acquiring lock for websocket {}: {:?}", ws_trigger.path, err); + } + }; +} + +struct SupersetVisitor<'a> { + key: &'a str, + value_to_check: &'a Value, +} + +impl<'de, 'a> Visitor<'de> for SupersetVisitor<'a> { + type Value = bool; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a JSON object with a specific key at the top level") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + while let Some(key) = map.next_key::()? { + if key == self.key { + // Deserialize the value for the key and check if it's a superset + let json_value: Value = map.next_value()?; + tracing::info!("json_value: {:?}", json_value); + tracing::info!("value_to_check: {:?}", self.value_to_check); + return Ok(is_superset(&json_value, self.value_to_check)); + } else { + // Skip the value if it's not the one we're interested in + let _ = map.next_value::()?; + } + } + // If the key was not found, return false + Ok(false) + } +} + +// Function to check if json_value is a superset of value_to_check +fn is_superset(json_value: &Value, value_to_check: &Value) -> bool { + match (json_value, value_to_check) { + (Value::Object(json_map), Value::Object(check_map)) => { + // Check that all keys and values in check_map exist and match in json_map + check_map.iter().all(|(k, v)| { + json_map + .get(k) + .map_or(false, |json_val| is_superset(json_val, v)) + }) + } + (Value::Array(json_array), Value::Array(check_array)) => { + // Check that all elements in check_array exist in json_array + check_array.iter().all(|check_item| { + json_array + .iter() + .any(|json_item| is_superset(json_item, check_item)) + }) + } + _ => json_value == value_to_check, + } +} + +// A function to deserialize and check if the value at the given key is a superset of a passed value +fn is_value_superset<'a, 'de, D>( + deserializer: D, + key: &'a str, + value_to_check: &'a Value, +) -> Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(SupersetVisitor { key, value_to_check }) +} + +async fn listen_to_websocket( + ws_trigger: WebsocketTrigger, + db: DB, + rsmq: Option, + mut killpill_rx: tokio::sync::broadcast::Receiver<()>, +) -> () { + async fn update_ping(db: DB, ws_trigger: &WebsocketTrigger, error: Option<&str>) -> Option<()> { + match sqlx::query_scalar!( + "UPDATE websocket_trigger SET last_server_ping = now(), error = $1 WHERE workspace_id = $2 AND path = $3 AND server_id = $4 AND enabled IS TRUE RETURNING 1", + error, + ws_trigger.workspace_id, + ws_trigger.path, + *INSTANCE_NAME + ).fetch_optional(&db).await { + Ok(updated) => { + if updated.flatten().is_none() { + tracing::info!("Websocket {} changed, disabled, or deleted, stopping...", ws_trigger.url); + return None; + } + }, + Err(err) => { + tracing::warn!("Error updating ping of websocket {}: {:?}", ws_trigger.url, err); + } + }; + + Some(()) + } + + let url = ws_trigger.url.as_str(); + + #[derive(Deserialize)] + struct JsonFilter { + key: String, + value: serde_json::Value, + } + + #[derive(Deserialize)] + #[serde(untagged)] + enum Filter { + JsonFilter(JsonFilter), + } + let filters: Vec = ws_trigger + .filters + .iter() + .filter_map(|m| serde_json::from_value(m.clone()).ok()) + .collect_vec(); + + loop { + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + }, + connection = connect_async(url) => { + match connection { + Ok((ws_stream, _)) => { + tracing::info!("Listening to websocket {}", url); + if let None = update_ping(db.clone(), &ws_trigger, None).await { + return; + } + let mut last_ping = tokio::time::Instant::now(); + let (_, mut read) = ws_stream.split(); + loop { + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + } + msg = read.next() => { + if let Some(msg) = msg { + if last_ping.elapsed() > tokio::time::Duration::from_secs(5) { + if let None = update_ping(db.clone(), &ws_trigger, None).await { + return; + } + last_ping = tokio::time::Instant::now(); + } + match msg { + Ok(msg) => { + match msg { + tokio_tungstenite::tungstenite::Message::Text(text) => { + let mut should_handle = true; + for filter in &filters { + match filter { + Filter::JsonFilter(JsonFilter { key, value }) => { + let mut deserializer = serde_json::Deserializer::from_str(text.as_str()); + should_handle = match is_value_superset(&mut deserializer, key, &value) { + Ok(filter_match) => { + filter_match + }, + Err(err) => { + tracing::warn!("Error deserializing filter for websocket {}: {:?}", url, err); + false + } + }; + } + } + if !should_handle { + break; + } + } + if should_handle { + let db_ = db.clone(); + let rsmq_ = rsmq.clone(); + let ws_trigger_ = ws_trigger.clone(); + tokio::spawn(async move { + let url = ws_trigger_.url.clone(); + if let Err(err) = run_job(db_, rsmq_, ws_trigger_, text).await { + tracing::error!("Error running job on websocket {}: {:?}", url, err); + }; + }); + } + }, + _ => {} + } + }, + Err(err) => { + tracing::error!("Error reading from websocket {}: {:?}", url, err); + } + } + } else { + tracing::error!("Websocket {} closed", url); + if let None = + update_ping(db.clone(), &ws_trigger, Some("Websocket closed")).await + { + return; + } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + break; + } + }, + _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => { + if let None = update_ping(db.clone(), &ws_trigger, None).await { + return; + } + last_ping = tokio::time::Instant::now(); + }, + } + } + } + Err(err) => { + tracing::error!("Error connecting to websocket {}: {:?}", url, err); + if let None = + update_ping(db.clone(), &ws_trigger, Some(err.to_string().as_str())).await + { + return; + } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + } + } + } + } + } +} + +async fn run_job( + db: DB, + rsmq: Option, + trigger: WebsocketTrigger, + msg: String, +) -> anyhow::Result<()> { + let args = PushArgsOwned { + args: HashMap::from([("msg".to_string(), to_raw_value(&msg))]), + extra: Some(HashMap::from([( + "wm_trigger".to_string(), + to_raw_value(&serde_json::json!({"kind": "websocket"})), + )])), + }; + let label_prefix = Some(format!("ws-{}-", trigger.path)); + + let authed = fetch_api_authed( + trigger.edited_by.clone(), + trigger.email.clone(), + &trigger.workspace_id, + &db, + "anonymous".to_string(), + ) + .await?; + + let user_db = UserDB::new(db.clone()); + + let run_query = RunJobQuery::default(); + + if trigger.is_flow { + run_wait_result_flow_by_path_internal( + db, + run_query, + StripPath(trigger.script_path.to_owned()), + authed, + rsmq, + user_db, + args, + trigger.workspace_id.clone(), + label_prefix, + ) + .await?; + } else { + run_wait_result_script_by_path_internal( + db, + run_query, + StripPath(trigger.script_path.to_owned()), + authed, + rsmq, + user_db, + trigger.workspace_id.clone(), + args, + label_prefix, + ) + .await?; + } + + Ok(()) +} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 95cd97fad0..cf48781331 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -116,7 +116,8 @@ pub fn workspaced_service() -> Router { .route("/get_workspace_name", get(get_workspace_name)) .route("/change_workspace_name", post(change_workspace_name)) .route("/change_workspace_id", post(change_workspace_id)) - .route("/usage", get(get_usage)); + .route("/usage", get(get_usage)) + .route("/used_triggers", get(get_used_triggers)); #[cfg(feature = "stripe")] { @@ -1488,6 +1489,30 @@ async fn set_encryption_key( return Ok(()); } +#[derive(Serialize)] +struct UsedTriggers { + pub websocket_used: bool, + pub http_routes_used: bool, +} + +async fn get_used_triggers( + authed: ApiAuthed, + Extension(user_db): Extension, + Path(w_id): Path, +) -> JsonResult { + let mut tx = user_db.begin(&authed).await?; + let websocket_used = sqlx::query_as!( + UsedTriggers, + r#"SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as "websocket_used!", EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as "http_routes_used!""#, + w_id, + ) + .fetch_one(&mut *tx) + .await?; + tx.commit().await?; + + Ok(Json(websocket_used)) +} + async fn list_workspaces_as_super_admin( authed: ApiAuthed, Extension(db): Extension, diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index f7cfe1c9ee..6feceb8d51 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -640,9 +640,7 @@ pub async fn get_logs_from_store( if log_offset > 0 { if let Some(file_index) = log_file_index.clone() { - tracing::debug!("Getting logs from store: {file_index:?}"); if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() { - tracing::debug!("object store client present, streaming from there"); let logs = logs.to_string(); let stream = async_stream::stream! { diff --git a/backend/windmill-worker/loader.bun.js b/backend/windmill-worker/loader.bun.js index b6efb68774..ce75cbfc15 100644 --- a/backend/windmill-worker/loader.bun.js +++ b/backend/windmill-worker/loader.bun.js @@ -8,29 +8,65 @@ const p = { "localhost", "127.0.0.1" ); + const w_id = "W_ID"; const current_path = "CURRENT_PATH"; + const token = "TOKEN"; + + const cdir = resolve("./"); + const cdirNoPrivate = cdir.replace(/^\/private/, ""); // for macos + const filterResolve = new RegExp( + `^(?!\\.\/main\\.ts)(?!${cdir}\/main\\.ts)(?!(?:/private)?${cdirNoPrivate}\/wrapper\\.mjs).*\\.ts$` + ); + + let cdirNodeModules = `${cdir}/node_modules/`; + + const filterLoad = new RegExp(`^${cdir}\/main\\.ts$`); + const transpiler = new Bun.Transpiler({ + loader: "tsx", + }); + + function replaceRelativeImports(code) { + const imports = transpiler.scanImports(code); + for (const imp of imports) { + if (imp.kind == "import-statement") { + if (imp.path.startsWith(".") && !imp.path.endsWith(".ts")) { + code = code.replaceAll(imp.path, imp.path + ".ts"); + } + } + } + return { + contents: code, + }; + } + + build.onLoad({ filter: filterLoad }, async (args) => { + const code = readFileSync(args.path, "utf8"); + return replaceRelativeImports(code); + }); build.onLoad({ filter: /.*\.url$/ }, async (args) => { const url = readFileSync(args.path, "utf8"); - const contents = await ( - await fetch(url, { - method: "GET", - headers: { Authorization: "Bearer TOKEN" }, - }) - ).text(); + const req = await fetch(url, { + method: "GET", + headers: { + Authorization: "Bearer " + token, + }, + }); + if (!req.ok) { + throw new Error( + `Failed to find relative import at ${url}`, + req.statusText + ); + } + const contents = await req.text(); return { - contents, + contents: replaceRelativeImports(contents).contents, loader: "tsx", }; }); - const cdir = resolve("./"); - const cdirNoPrivate = cdir.replace(/^\/private/, ""); // for macos - const filter = new RegExp( - `^(?!\\.\/main\\.ts)(?!${cdir}\/main\\.ts)(?!(?:/private)?${cdirNoPrivate}\/wrapper\\.mjs).*\\.ts$` - ); - let cdirNodeModules = `${cdir}/node_modules/`; - build.onResolve({ filter }, (args) => { + + build.onResolve({ filter: filterResolve }, (args) => { if (args.importer?.startsWith(cdirNodeModules)) { return undefined; } @@ -41,9 +77,10 @@ const p = { const isRelative = !args.path.startsWith("/"); + let endExt = args.path.endsWith(".ts") ? "" : ".ts"; const url = isRelative - ? `${base_internal_url}/api/w/${w_id}/scripts/RAW_GET_ENDPOINT/p/${file_path}/../${args.path}` - : `${base_internal_url}/api/w/${w_id}/scripts/RAW_GET_ENDPOINT/p/${args.path}`; + ? `${base_internal_url}/api/w/${w_id}/scripts/raw_unpinned/p/${file_path}/../${args.path}${endExt}` + : `${base_internal_url}/api/w/${w_id}/scripts/raw_unpinned/p/${args.path}${endExt}`; const file = isRelative ? resolve("./" + file_path + "/../" + args.path + ".url") : resolve("./" + args.path + ".url"); diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index a5d2740c7c..fda9176483 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.409.2"; +export const VERSION = "v1.411.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/local_encryption.ts b/cli/local_encryption.ts new file mode 100644 index 0000000000..0a70b0742b --- /dev/null +++ b/cli/local_encryption.ts @@ -0,0 +1,104 @@ +import { log } from "./deps.ts"; +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"] + ); + } + + + + // 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); + } + +// // 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 b9c92ece0d..0fc9384525 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.409.2"; +export const VERSION = "1.411.1"; const command = new Command() .name("wmill") @@ -101,9 +101,13 @@ const command = new Command() "wmill.yaml", yamlStringify({ defaultTs: "bun", - includes: ["**"], + includes: ["f/**"], excludes: [], codebases: [], + skipVariables: true, + skipResources: true, + skipSecrets: true, + includeSchedules: false, }) ); log.info(colors.green("wmill.yaml created")); @@ -213,9 +217,11 @@ function isMain() { const isMain = import.meta.main; if (isMain) { if (!Deno.args.includes("completions")) { - log.warn( - "Using the deno runtime for the Windmill CLI is deprecated, you can now use node: deno uninstall wmill && npm install -g windmill-cli" - ); + if (Deno.env.get("SKIP_DENO_DEPRECATION_WARNING") !== "true") { + log.warn( + "Using the deno runtime for the Windmill CLI is deprecated, you can now use node: deno uninstall wmill && npm install -g windmill-cli. To skip this warning set SKIP_DENO_DEPRECATION_WARNING=true" + ); + } } } return isMain; diff --git a/cli/settings.ts b/cli/settings.ts index 328a6d0e25..f9dbac1fa6 100644 --- a/cli/settings.ts +++ b/cli/settings.ts @@ -9,6 +9,7 @@ import { deepEqual } from "./utils.ts"; import * as wmill from "./gen/services.gen.ts"; import { Config, GlobalSetting } from "./gen/types.gen.ts"; import { removeWorkerPrefix } from "./worker_groups.ts"; +import process from "node:process"; export interface SimplifiedSettings { // slack_team_id?: string; @@ -112,10 +113,10 @@ export async function pushWorkspaceSettings( workspace, requestBody: localSettings.auto_invite_enabled ? { - operator: localSettings.auto_invite_as === "operator", - invite_all: true, - auto_add: localSettings.auto_invite_mode === "add", - } + operator: localSettings.auto_invite_as === "operator", + invite_all: true, + auto_add: localSettings.auto_invite_mode === "add", + } : {}, }); } catch (_) { @@ -127,10 +128,10 @@ export async function pushWorkspaceSettings( workspace, requestBody: localSettings.auto_invite_enabled ? { - operator: localSettings.auto_invite_as === "operator", - invite_all: false, - auto_add: localSettings.auto_invite_mode === "add", - } + operator: localSettings.auto_invite_as === "operator", + invite_all: false, + auto_add: localSettings.auto_invite_mode === "add", + } : {}, }); } @@ -155,7 +156,7 @@ export async function pushWorkspaceSettings( settings.error_handler_extra_args ) || localSettings.error_handler_muted_on_cancel !== - settings.error_handler_muted_on_cancel + settings.error_handler_muted_on_cancel ) { log.debug(`Updating error handler...`); await wmill.editErrorHandler({ @@ -259,27 +260,69 @@ export async function pushWorkspaceKey( } } +const INSTANCE_SETTINGS_PATH = "instance_settings.yaml"; + export async function readInstanceSettings() { let localSettings: GlobalSetting[] = []; try { - localSettings = (await yamlParseFile( - "instance_settings.yaml" - )) as GlobalSetting[]; + localSettings = (await yamlParseFile(INSTANCE_SETTINGS_PATH)) as GlobalSetting[]; } catch { - log.warn("No instance_settings.yaml found"); + log.warn(`No ${INSTANCE_SETTINGS_PATH} found`); } return localSettings; } + +import { decrypt, encrypt } from "./local_encryption.ts"; + +const SENSITIVE_FIELD: string[] = ["license_key", "jwt_secret"] + +async function processInstanceSettings(settings: GlobalSetting[], mode: "encode" | "decode"): Promise { + const encKey = process.env.WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY; + if (encKey) { + const res: GlobalSetting[] = [] + + for (const s of settings) { + if (SENSITIVE_FIELD.includes(s.name) && typeof s.value === "string") { + res.push(await processField(s, "value", encKey, mode) as GlobalSetting); + } else if (s.name == "oauths") { + if (typeof s.value === "object") { + const oauths = s.value as { [key: string]: any }; + for (const [k, v] of Object.entries(oauths)) { + oauths[k] = await processField(v, "secret", encKey, mode); + } + res.push(s); + } else { + log.warn(`Unexpected oauths value type: ${typeof s.value}`); + res.push(s); + } + } else { + res.push(s); + } + } + return res; + } else { + log.warn("No encryption key found, skipping encryption. Recommend setting WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY"); + } + return settings; +} + +async function processField(obj: { [key: string]: any }, field: string, encKey: string, mode: "encode" | "decode"): Promise<{ [key: string]: any }> { + return { + ...obj, + [field]: mode === "encode" ? await encrypt(obj[field], encKey) : await decrypt(obj[field], encKey) as any, + } +} + export async function pullInstanceSettings(preview = false) { const remoteSettings = await wmill.listGlobalSettings(); if (preview) { const localSettings: GlobalSetting[] = await readInstanceSettings(); - + const processedSettings = await processInstanceSettings(remoteSettings, "encode"); return compareInstanceObjects( - remoteSettings, + processedSettings, localSettings, "name", "setting" @@ -287,12 +330,13 @@ export async function pullInstanceSettings(preview = false) { } else { log.info("Pulling settings from instance"); + const processedSettings = await processInstanceSettings(remoteSettings, "encode"); await Deno.writeTextFile( - "instance_settings.yaml", - yamlStringify(remoteSettings as any) + INSTANCE_SETTINGS_PATH, + yamlStringify(processedSettings) ); - log.info(colors.green("Settings written to instance_settings.yaml")); + log.info(colors.green(`Settings written to ${INSTANCE_SETTINGS_PATH}`)); } } @@ -302,6 +346,7 @@ export async function pushInstanceSettings( ) { const remoteSettings = await wmill.listGlobalSettings(); let localSettings: GlobalSetting[] = await readInstanceSettings(); + localSettings = await processInstanceSettings(localSettings, "decode"); if (baseUrl) { localSettings = localSettings.filter((s) => s.name !== "base_url"); diff --git a/cli/sync.ts b/cli/sync.ts index ce539b7a1f..342dda3162 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -618,8 +618,8 @@ export async function elementsToMap( for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { if (entry.isDirectory || entry.ignored) continue; const path = entry.path; - if (json && path.endsWith(".yaml")) continue; - if (!json && path.endsWith(".json")) continue; + if (json && path.endsWith(".yaml") && !isFileResource(path)) continue; + if (!json && path.endsWith(".json") && !isFileResource(path)) continue; const ext = json ? ".json" : ".yaml"; if (!skips.includeSchedules && path.endsWith(".schedule" + ext)) continue; if (!skips.includeUsers && path.endsWith(".user" + ext)) continue; diff --git a/docker/DockerfileSlim b/docker/DockerfileSlim index cb0284ff1d..a0190933a4 100644 --- a/docker/DockerfileSlim +++ b/docker/DockerfileSlim @@ -20,7 +20,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools # Install UV RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv -COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun # add the docker client to call docker from a worker if enabled COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ diff --git a/docker/DockerfileSlimEe b/docker/DockerfileSlimEe index 54a7b15764..ef3771c767 100644 --- a/docker/DockerfileSlimEe +++ b/docker/DockerfileSlimEe @@ -19,7 +19,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools # Install UV RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv -COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun +COPY --from=oven/bun:1.1.32 /usr/local/bin/bun /usr/bin/bun # add the docker client to call docker from a worker if enabled COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 87816755db..7761d4f4a6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.409.2", + "version": "1.411.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.409.2", + "version": "1.411.1", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", diff --git a/frontend/package.json b/frontend/package.json index 0a29d95564..dee26b8333 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.409.2", + "version": "1.411.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/ata/index.ts b/frontend/src/lib/ata/index.ts index 3af147b45c..5414df8b18 100644 --- a/frontend/src/lib/ata/index.ts +++ b/frontend/src/lib/ata/index.ts @@ -120,11 +120,16 @@ export const setupTypeAcquisition = (config: ATABootstrapConfig) => { ? f.raw : '/' + config.scriptPath + (f.raw.startsWith('../') ? '/../' : '/.') + f.raw let url = config.root + path - // console.log('FOO', config.scriptPath, path, f.raw) - console.log('fetching local file', url, f.raw) + let localPath = f.raw + if (f.raw.startsWith('.') && !f.raw.endsWith('.ts')) { + url += '.ts' + localPath += '.ts' + } + + console.log('fetching local file', url, f.raw, localPath) const res = await fetch(url) if (res.ok) { - config.delegate.localFile?.(await res.text(), f.raw) + config.delegate.localFile?.(await res.text(), localPath) } }) } diff --git a/frontend/src/lib/components/CronInput.svelte b/frontend/src/lib/components/CronInput.svelte index cee391930f..42fc3fecc4 100644 --- a/frontend/src/lib/components/CronInput.svelte +++ b/frontend/src/lib/components/CronInput.svelte @@ -112,26 +112,26 @@ // If using the basic editor, set the cron string based on the selected options if (executeEvery === 'second') { if (seconds > 0) { - nschedule = `*/${seconds} * * * *` + nschedule = `*/${seconds} * * * * *` } else { - nschedule = `* * * * *` + nschedule = `* * * * * *` } } else if (executeEvery === 'minute') { if (minutes > 0) { - nschedule = `0 */${minutes} * * *` + nschedule = `0 */${minutes} * * * *` } else { - nschedule = `* * * * *` + nschedule = `* * * * * *` } } else if (executeEvery === 'hour') { if (hours > 0) { - nschedule = `0 0 */${hours} * *` + nschedule = `0 0 */${hours} * * *` } else { - nschedule = `* * * * *` + nschedule = `* * * * * *` } } else if (executeEvery === 'day-month') { - nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} *` + nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} * *` } else if (executeEvery === 'month') { - nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} ${s_months}` + nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} ${s_months} *` } else if (executeEvery === 'day-week') { nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} * * ${s_daysOfWeek}` } @@ -214,7 +214,7 @@ type="text" id="cron-schedule" name="cron-schedule" - placeholder="*/30 * * * *" + placeholder="0 0 */1 * * *" bind:value={schedule} {disabled} /> diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 158f7bb762..5e3b52d867 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -475,9 +475,9 @@ const testStepStore = writable>({}) const selectedIdStore = writable('settings-metadata') - const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>( - 'webhooks' - ) + const selectedTriggerStore = writable< + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + >('webhooks') const primaryScheduleStore = writable(undefined) const triggersCount = writable(undefined) diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 09c6ff1209..6da81b66d3 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -25,10 +25,12 @@ encodeState, formatCron, orderedJsonStringify, - sleep + sleep, + type Value } from '$lib/utils' import { sendUserToast } from '$lib/toast' import { Drawer } from '$lib/components/common' + import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte' import { setContext, tick, type ComponentType } from 'svelte' import { writable, type Writable } from 'svelte/store' @@ -102,9 +104,30 @@ export let disableAi: boolean = false export let disabledFlowInputs = false export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined + export let version: number | undefined = undefined + + // Used by multiplayer deploy collision warning + let deployedValue: Value | undefined = undefined // Value to diff against + let deployedBy: string | undefined = undefined // Author + let confirmCallback: () => void = () => {} // What happens when user clicks `override` in warning + let open: boolean = false // Is confirmation modal open $: setContext('customUi', customUi) + let onLatest = true + async function compareVersions() { + if (version === undefined) { + return + } + const flowVersion = await FlowService.getFlowLatestVersion({ + workspace: $workspaceStore!, + path: $pathStore + }) + + onLatest = version === flowVersion?.id + + } + const dispatch = createEventDispatcher() const primaryScheduleStore = writable(savedPrimarySchedule) @@ -253,6 +276,46 @@ ) } + async function handleSaveFlow(deploymentMsg?: string) { + + await compareVersions(); + if (onLatest) { + // Handle directly + await saveFlow(deploymentMsg) + } else { + // We need it for diff + await syncWithDeployed() + + // Handle through confirmation modal + confirmCallback = async () => { + await saveFlow(deploymentMsg) + } + // Open confirmation modal + 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 saveFlow(deploymentMsg?: string): Promise { loadingSave = true try { @@ -330,7 +393,7 @@ requestBody: { enabled } }) } - } else if (scheduleExists) { + } else if (scheduleExists && !$triggersCount?.primary_schedule) { await ScheduleService.deleteSchedule({ workspace: $workspaceStore ?? '', path: $pathStore @@ -398,9 +461,9 @@ } const selectedIdStore = writable(selectedId ?? 'settings-metadata') - const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>( - 'webhooks' - ) + const selectedTriggerStore = writable< + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + >('webhooks') export function getSelectedId() { return $selectedIdStore @@ -420,7 +483,9 @@ selectedIdStore.set(selectedId) } - function selectTrigger(selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes') { + function selectTrigger( + selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + ) { selectedTriggerStore.set(selectedTrigger) } @@ -1122,6 +1187,15 @@ + + {#key renderCount} {#if !$userStore?.operator} @@ -1286,14 +1360,17 @@ color="light" variant="border" size="xs" - on:click={() => { + on:click={async () => { if (!savedFlow) { return } + + await syncWithDeployed() + diffDrawer?.openDrawer() diffDrawer?.setDiff({ mode: 'normal', - deployed: savedFlow, + deployed: deployedValue ?? savedFlow, draft: savedFlow['draft'], current: { ...$flowStore, path: $pathStore } }) @@ -1335,7 +1412,9 @@ loading={loadingSave} size="xs" startIcon={{ icon: Save }} - on:click={() => saveFlow()} + on:click={async () => { + await handleSaveFlow() + }} dropdownItems={!newFlow ? dropdownItems : undefined} > Deploy @@ -1346,16 +1425,16 @@ type="text" placeholder="Deployment message" bind:value={deploymentMsg} - on:keydown={(e) => { + on:keydown={async (e) => { if (e.key === 'Enter') { - saveFlow(deploymentMsg) + await handleSaveFlow(deploymentMsg) } }} bind:this={msgInput} /> {/if} {#if customUi?.topBar?.path != false} @@ -1150,14 +1227,16 @@ color="light" variant="border" size="xs" - on:click={() => { + on:click={async () => { if (!savedScript) { return } + await syncWithDeployed() + diffDrawer?.openDrawer() diffDrawer?.setDiff({ mode: 'normal', - deployed: savedScript, + deployed: deployedValue ?? savedScript, draft: savedScript['draft'], current: script }) @@ -1202,7 +1281,7 @@ size="xs" disabled={!fullyLoaded} startIcon={{ icon: Save }} - on:click={() => editScript(false)} + on:click={() => handleEditScript(false)} dropdownItems={computeDropdownItems(initialPath)} > Deploy @@ -1216,13 +1295,13 @@ bind:this={msgInput} on:keydown={(e) => { if (e.key === 'Enter') { - editScript(false, deploymentMsg) + handleEditScript(false, deploymentMsg) } }} /> + {/if} + + diff --git a/frontend/src/lib/components/details/DetailPageDetailPanel.svelte b/frontend/src/lib/components/details/DetailPageDetailPanel.svelte index 4ce13fca2f..4d56fba8f3 100644 --- a/frontend/src/lib/components/details/DetailPageDetailPanel.svelte +++ b/frontend/src/lib/components/details/DetailPageDetailPanel.svelte @@ -6,7 +6,13 @@ import FlowViewerInner from '../FlowViewerInner.svelte' import DetailPageTriggerPanel from './DetailPageTriggerPanel.svelte' - export let triggerSelected: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' = 'webhooks' + export let triggerSelected: + | 'webhooks' + | 'emails' + | 'schedules' + | 'cli' + | 'routes' + | 'websockets' = 'webhooks' export let flow_json: any | undefined = undefined export let isOperator: boolean = false @@ -25,13 +31,12 @@ {/if} {#if flow_json} Export + {:else} + Script {/if} {#if selected == 'flow_step'} Step {/if} - {#if !flow_json && !isOperator} - Schema - {/if}
@@ -41,10 +46,14 @@ + + + + diff --git a/frontend/src/lib/components/details/DetailPageLayout.svelte b/frontend/src/lib/components/details/DetailPageLayout.svelte index 60022e4e62..ec0a5bda16 100644 --- a/frontend/src/lib/components/details/DetailPageLayout.svelte +++ b/frontend/src/lib/components/details/DetailPageLayout.svelte @@ -19,9 +19,9 @@ let clientWidth = window.innerWidth const primaryScheduleStore = writable(undefined) - const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>( - 'webhooks' - ) + const selectedTriggerStore = writable< + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + >('webhooks') setContext('TriggerContext', { selectedTrigger: selectedTriggerStore, @@ -48,13 +48,13 @@ > + - + - @@ -69,6 +69,10 @@ {#if !isOperator} Triggers {/if} + {#if !flow_json} + Script + {/if} + @@ -81,11 +85,16 @@ + + + + +
diff --git a/frontend/src/lib/components/details/DetailPageTriggerPanel.svelte b/frontend/src/lib/components/details/DetailPageTriggerPanel.svelte index cbf1e704b0..d09a92ae81 100644 --- a/frontend/src/lib/components/details/DetailPageTriggerPanel.svelte +++ b/frontend/src/lib/components/details/DetailPageTriggerPanel.svelte @@ -1,42 +1,54 @@ - - + + Webhooks - - + + Schedules - - + + HTTP + + + + Websockets + + - - + + Email - - + + CLI @@ -52,6 +64,8 @@ {:else if triggerSelected === 'schedules'} + {:else if triggerSelected === 'websockets'} + {:else if triggerSelected === 'cli'} {/if} diff --git a/frontend/src/lib/components/flows/content/FlowPathViewer.svelte b/frontend/src/lib/components/flows/content/FlowPathViewer.svelte index 109e833c0f..a55ad17c47 100644 --- a/frontend/src/lib/components/flows/content/FlowPathViewer.svelte +++ b/frontend/src/lib/components/flows/content/FlowPathViewer.svelte @@ -1,15 +1,33 @@ diff --git a/frontend/src/lib/components/sidebar/SidebarContent.svelte b/frontend/src/lib/components/sidebar/SidebarContent.svelte index 3b59a5d636..833272f6be 100644 --- a/frontend/src/lib/components/sidebar/SidebarContent.svelte +++ b/frontend/src/lib/components/sidebar/SidebarContent.svelte @@ -21,7 +21,8 @@ ServerCog, Settings, UserCog, - Plus + Plus, + Unplug } from 'lucide-svelte' import Menu from '../common/menu/MenuV2.svelte' import MenuButton from './MenuButton.svelte' @@ -77,6 +78,13 @@ icon: Route, disabled: $userStore?.operator, kind: 'http' + }, + { + label: 'Websockets', + href: '/websocket_triggers', + icon: Unplug, + disabled: $userStore?.operator, + kind: 'ws' } ] diff --git a/frontend/src/lib/components/triggers.ts b/frontend/src/lib/components/triggers.ts index a464acce9e..a7a7735160 100644 --- a/frontend/src/lib/components/triggers.ts +++ b/frontend/src/lib/components/triggers.ts @@ -10,7 +10,7 @@ export type ScheduleTrigger = { } export type TriggerContext = { - selectedTrigger: Writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'> + selectedTrigger: Writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'> primarySchedule: Writable triggersCount: Writable } diff --git a/frontend/src/lib/components/triggers/RouteEditorInner.svelte b/frontend/src/lib/components/triggers/RouteEditorInner.svelte index 19846e5c24..5cf683515b 100644 --- a/frontend/src/lib/components/triggers/RouteEditorInner.svelte +++ b/frontend/src/lib/components/triggers/RouteEditorInner.svelte @@ -39,6 +39,7 @@ itemKind = isFlow ? 'flow' : 'script' edit = true dirtyPath = false + dirtyRoutePath = false await loadTrigger() } catch (err) { sendUserToast(`Could not load route: ${err}`, true) @@ -58,6 +59,7 @@ requires_auth = false initialRoutePath = '' route_path = '' + dirtyRoutePath = false http_method = 'post' initialScriptPath = '' fixedScriptPath = fixedScriptPath_ ?? '' @@ -73,6 +75,7 @@ let path: string = '' let pathError = '' let routeError = '' + let dirtyRoutePath = false let is_async = false let requires_auth = false let initialRoutePath = '' @@ -239,6 +242,9 @@ class={routeError === '' ? '' : 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'} + on:input={() => { + dirtyRoutePath = true + }} /> @@ -272,7 +278,10 @@ }} /> -
{routeError}
+ +
{dirtyRoutePath ? routeError : ''}
diff --git a/frontend/src/lib/components/triggers/TriggersEditor.svelte b/frontend/src/lib/components/triggers/TriggersEditor.svelte index d2f3a99913..e442624ed8 100644 --- a/frontend/src/lib/components/triggers/TriggersEditor.svelte +++ b/frontend/src/lib/components/triggers/TriggersEditor.svelte @@ -10,6 +10,7 @@ import FlowCard from '../flows/common/FlowCard.svelte' import { getContext } from 'svelte' import type { TriggerContext } from '$lib/components/triggers' + import WebsocketTriggersPanel from './WebsocketTriggersPanel.svelte' export let noEditor: boolean export let newItem = false @@ -27,6 +28,7 @@ Webhooks Schedules Routes + Websockets Email @@ -60,6 +62,12 @@ {/if} + {#if $selectedTrigger === 'websockets'} +
+ +
+ {/if} + {#if $selectedTrigger === 'schedules'}
+ import { tick } from 'svelte' + import WebsocketTriggerEditorInner from './WebsocketTriggerEditorInner.svelte' + + let open = false + export async function openEdit(ePath: string, isFlow: boolean) { + open = true + await tick() + drawer?.openEdit(ePath, isFlow) + } + + export async function openNew(is_flow: boolean, initial_script_path?: string) { + open = true + await tick() + drawer?.openNew(is_flow, initial_script_path) + } + + let drawer: WebsocketTriggerEditorInner + + +{#if open} + +{/if} diff --git a/frontend/src/lib/components/triggers/WebsocketTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/WebsocketTriggerEditorInner.svelte new file mode 100644 index 0000000000..39f9ce3cae --- /dev/null +++ b/frontend/src/lib/components/triggers/WebsocketTriggerEditorInner.svelte @@ -0,0 +1,338 @@ + + + + + + {#if !drawerLoading && can_write} + {#if edit} +
+ { + await WebsocketTriggerService.setWebsocketTriggerEnabled({ + path: initialPath, + workspace: $workspaceStore ?? '', + requestBody: { enabled: e.detail } + }) + sendUserToast( + `${e.detail ? 'enabled' : 'disabled'} websocket trigger ${initialPath}` + ) + }} + /> +
+ {/if} + + {/if} +
+ {#if drawerLoading} + + {:else} + + {#if edit} + Changes can take up to 30 seconds to take effect. + {:else} + New websocket triggers can take up to 30 seconds to start listening. + {/if} + +
+
+ +
+ +
+
+ +
+
+ +
+

+ Pick a script or flow to be triggered +

+
+ +
+
+ +
+

+ Filters will limit the execution of the trigger to only messages that match all + criteria.
+ The JSON filter checks if the value at the key is equal or a superset of the filter value. +

+
+ {#each filters as v, i} +
+
+
+ +
+ + + +
+ +
+ {/each} + +
+ +
+
+
+
+ {/if} +
+
diff --git a/frontend/src/lib/components/triggers/WebsocketTriggersPanel.svelte b/frontend/src/lib/components/triggers/WebsocketTriggersPanel.svelte new file mode 100644 index 0000000000..f209b4fbb8 --- /dev/null +++ b/frontend/src/lib/components/triggers/WebsocketTriggersPanel.svelte @@ -0,0 +1,108 @@ + + + { + loadTriggers() + }} + bind:this={wsTriggerEditor} +/> + +
+ {#if !newItem} + {#if isCloudHosted()} + + Websocket triggers are disabled in the multi-tenant cloud. + + {:else if $userStore?.is_admin || $userStore?.is_super_admin} + + {:else} + + {/if} + {/if} + + {#if wsTriggers} + {#if wsTriggers.length == 0} +
No WS triggers
+ {:else} +
+ {#each wsTriggers as wsTriggers (wsTriggers.path)} +
+
{wsTriggers.path}
+
+ {wsTriggers.url} +
+
+ +
+
+ {/each} +
+ {/if} + {:else} + + {/if} + + {#if newItem} + + Deploy the {isFlow ? 'flow' : 'script'} to add WS triggers. + + {/if} +
diff --git a/frontend/src/lib/relative_imports.ts b/frontend/src/lib/relative_imports.ts index 3923a1e648..fd4b44c9ba 100644 --- a/frontend/src/lib/relative_imports.ts +++ b/frontend/src/lib/relative_imports.ts @@ -26,5 +26,5 @@ export function approximateFindPythonRelativePath(code: string) { // Use match to find all matches in the code const matches = code.match(regex) - return [...(matches?.entries() ?? [])] || [] + return [...(matches?.entries() ?? [])] } diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index b940aba39e..f584ee9f19 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -514,7 +514,7 @@ export async function main(approver?: string) { const BUN_PREPROCESSOR_MODULE_CODE = ` export async function preprocessor( wm_trigger: { - kind: 'http' | 'email' | 'webhook', + kind: 'http' | 'email' | 'webhook' | 'websocket', http?: { route: string // The route path, e.g. "/users/:id" path: string // The actual path called, e.g. "/users/123" @@ -535,7 +535,7 @@ export async function preprocessor( const DENO_PREPROCESSOR_MODULE_CODE = ` export async function preprocessor( wm_trigger: { - kind: 'http' | 'email' | 'wehbook', + kind: 'http' | 'email' | 'wehbook' | 'websocket', http?: { route: string // The route path, e.g. "/users/:id" path: string // The actual path called, e.g. "/users/123" @@ -591,7 +591,7 @@ class Http(TypedDict): headers: dict[str, str] class WmTrigger(TypedDict): - kind: Literal["http", "email", "webhook"] + kind: Literal["http", "email", "webhook", "websocket"] http: Http | None def preprocessor( diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 3eae42916f..ca7a98cfaf 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -8,7 +8,6 @@ RawAppService, ScriptService, SettingService, - HttpTriggerService, UserService, WorkspaceService } from '$lib/gen' @@ -187,8 +186,17 @@ } async function loadUsedTriggerKinds() { - const httpUsed = await HttpTriggerService.used({ workspace: $workspaceStore ?? '' }) - $usedTriggerKinds = httpUsed ? ['http'] : [] + let usedKinds: string[] = [] + const { http_routes_used, websocket_used } = await WorkspaceService.getUsedTriggers({ + workspace: $workspaceStore ?? '' + }) + if (http_routes_used) { + usedKinds.push('http') + } + if (websocket_used) { + usedKinds.push('ws') + } + $usedTriggerKinds = usedKinds } function pathInAppMode(pathname: string | undefined): boolean { diff --git a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte index 56125d9345..9409ecca89 100644 --- a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte @@ -15,6 +15,7 @@ import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte' import type { ScheduleTrigger } from '$lib/components/triggers' + let version: undefined | number = undefined; let nodraft = $page.url.searchParams.get('nodraft') const initialState = nodraft ? undefined : localStorage.getItem(`flow-${$page.params.path}`) let stateLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined @@ -65,6 +66,13 @@ let flow: Flow let statePath = stateLoadedFromUrl?.path if (stateLoadedFromUrl != undefined && statePath == $page.params.path) { + // Currently there is no way to get version of flow with flow. + // So we have to request it here + version = (await FlowService.getFlowLatestVersion({ + workspace: $workspaceStore!, + path: statePath + })).id; + savedFlow = await FlowService.getFlowByPathWithDraft({ workspace: $workspaceStore!, path: statePath @@ -103,6 +111,13 @@ ]) } } else { + // Currently there is no way to get version of flow with flow. + // So we have to request it here + version = (await FlowService.getFlowLatestVersion({ + workspace: $workspaceStore!, + path: $page.params.path + })).id; + const flowWithDraft = await FlowService.getFlowByPathWithDraft({ workspace: $workspaceStore!, path: $page.params.path @@ -230,6 +245,7 @@ bind:savedFlow {diffDrawer} {savedPrimarySchedule} + bind:version > diff --git a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte index 76ea250186..64b003a8a8 100644 --- a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte @@ -60,6 +60,7 @@ import json from 'svelte-highlight/languages/json' import { writable } from 'svelte/store' import TriggersBadge from '$lib/components/graph/renderers/triggers/TriggersBadge.svelte' + import WebsocketTriggersPanel from '$lib/components/triggers/WebsocketTriggersPanel.svelte' let flow: Flow | undefined let can_write = false @@ -473,6 +474,7 @@
+ +
+ +
+
- x.path.startsWith(ownerFilter + '/' ?? '') && + x.path.startsWith(ownerFilter + '/') && filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders) ) : triggers?.filter( (x) => - x.script_path.startsWith(ownerFilter + '/' ?? '') && + x.script_path.startsWith(ownerFilter + '/') && filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders) ) : triggers?.filter((x) => @@ -104,7 +104,7 @@ } $: owners = - selectedFilterKind === 'route' + selectedFilterKind === 'trigger' ? Array.from( new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? []) ).sort() @@ -133,7 +133,7 @@ let queryFilterKind = url.searchParams.get(TRIGGER_PATH_KIND_FILTER_SETTING) let queryFilterUserFolders = url.searchParams.get(FILTER_USER_FOLDER_SETTING_NAME) if (queryFilterKind) { - selectedFilterKind = queryFilterKind as 'route' | 'script_flow' + selectedFilterKind = queryFilterKind as 'trigger' | 'script_flow' } if (queryFilterUserFolders) { filterUserFolders = queryFilterUserFolders == 'true' @@ -174,7 +174,7 @@
Filter by path of
- +
diff --git a/frontend/src/routes/(root)/(logged)/schedules/+page.svelte b/frontend/src/routes/(root)/(logged)/schedules/+page.svelte index e9225d405d..1f8c9e84a5 100644 --- a/frontend/src/routes/(root)/(logged)/schedules/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/schedules/+page.svelte @@ -172,13 +172,13 @@ ? selectedFilterKind === 'schedule' ? schedules?.filter( (x) => - x.path.startsWith(ownerFilter + '/' ?? '') && + x.path.startsWith(ownerFilter + '/') && filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders) && filterItemsBasedOnEnabledDisabled(x, filterEnabledDisabled) ) : schedules?.filter( (x) => - x.script_path.startsWith(ownerFilter + '/' ?? '') && + x.script_path.startsWith(ownerFilter + '/') && filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders) && filterItemsBasedOnEnabledDisabled(x, filterEnabledDisabled) ) diff --git a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte index 152f2dde3a..66641c18d3 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte @@ -21,7 +21,6 @@ import { enterpriseLicense, hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores' import { isDeployable, ALL_DEPLOYABLE } from '$lib/utils_deployable' - import SchemaViewer from '$lib/components/SchemaViewer.svelte' import { onDestroy } from 'svelte' import HighlightCode from '$lib/components/HighlightCode.svelte' import { @@ -84,6 +83,7 @@ import json from 'svelte-highlight/languages/json' import { writable } from 'svelte/store' import TriggersBadge from '$lib/components/graph/renderers/triggers/TriggersBadge.svelte' + import WebsocketTriggersPanel from '$lib/components/triggers/WebsocketTriggersPanel.svelte' let script: Script | undefined let topHash: string | undefined @@ -685,17 +685,6 @@
- -
- - -
-
{#if args} + +
+ +
+
- +
Code Lockfile - - - Inputs - - The jsonschema defines the constraints that the payload must respect to be - compatible with the input parameters of this script. The UI form is generated - automatically from the script jsonschema. See - - jsonschema documentation - - - - + Schema
@@ -802,9 +784,15 @@ {/if}
- -
- + +
+ +
diff --git a/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.js b/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.js new file mode 100644 index 0000000000..8e3a86c5f4 --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.js @@ -0,0 +1,5 @@ +export function load() { + return { + stuff: { title: 'WS triggers' } + } +} diff --git a/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.svelte new file mode 100644 index 0000000000..49a8ec6e52 --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/websocket_triggers/+page.svelte @@ -0,0 +1,422 @@ + + + + + (x.summary ?? '') + ' ' + x.path + ' (' + x.script_path + ')'} +/> + + + + + + + {#if isCloudHosted()} + + Websocket triggers are disabled in the multi-tenant cloud. + +
+ {/if} +
+
+ +
+
Filter by path of
+ + + + +
+ + +
+ {#if $userStore?.is_super_admin && $userStore.username.includes('@')} + + {:else if $userStore?.is_admin || $userStore?.is_super_admin} + + {/if} +
+
+ {#if loading} + {#each new Array(6) as _} + + {/each} + {:else if !triggers?.length} +
No websocket triggers
+ {:else if items?.length} +
+ {#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, url, is_flow, extra_perms, canWrite, marked, error, last_server_ping, enabled } (path)} + {@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`} + {@const wsStatus = status[path] ?? { error, last_server_ping }} + +
+
+ + + websocketTriggerEditor?.openEdit(path, is_flow)} + class="min-w-0 grow hover:underline decoration-gray-400" + > +
+ {#if marked} + + {@html marked} + + {:else} + {url} + {/if} +
+
+ {path} +
+
+ runnable: {script_path} +
+
+ + + +
+ {#if enabled} + {@const ping = wsStatus.last_server_ping + ? new Date(wsStatus.last_server_ping) + : undefined} + {#if !ping || ping.getTime() < new Date().getTime() - 15 * 1000 || wsStatus.error} + + + + + +
+ Websocket is not connected{wsStatus.error ? ': ' + wsStatus.error : ''} +
+
+ {:else} + + + + +
Websocket is connected
+
+ {/if} + {/if} +
+ + { + setTriggerEnabled(path, e.detail) + }} + /> + +
+ + { + goto(href) + } + }, + { + displayName: 'Delete', + type: 'delete', + icon: Trash, + disabled: !canWrite, + action: async () => { + await WebsocketTriggerService.deleteWebsocketTrigger({ + workspace: $workspaceStore ?? '', + path + }) + loadTriggers() + } + }, + { + displayName: canWrite ? 'Edit' : 'View', + icon: canWrite ? Pen : Eye, + action: () => { + websocketTriggerEditor?.openEdit(path, is_flow) + } + }, + { + displayName: 'Audit logs', + icon: Eye, + href: `${base}/audit_logs?resource=${path}` + }, + { + displayName: canWrite ? 'Share' : 'See Permissions', + icon: Share, + action: () => { + shareModal.openDrawer(path, 'websocket_trigger') + } + } + ]} + /> +
+
+
+
edited by {edited_by}
the {displayDate(edited_at)}
+
+ {/each} +
+ {:else} + + {/if} +
+ {#if items && items?.length > 15 && nbDisplayed < items.length} + {nbDisplayed} items out of {items.length} + + {/if} + + + { + loadTriggers() + }} +/> diff --git a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte index 24beda13ed..14be8dbcaa 100644 --- a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte @@ -1,6 +1,5 @@ @@ -652,7 +599,9 @@ { - setQueryWithoutLoad($page.url, [{ key: 'tab', value: tab }], 0) + // setQueryWithoutLoad($page.url, [{ key: 'tab', value: tab }], 0) + $page.url.searchParams.set('tab', tab) + goto(`?${$page.url.searchParams.toString()}`) }} > @@ -933,6 +882,7 @@ Workspace error handler is a Windmill EE feature. It enables using your current Slack connection or a custom script to send notifications anytime any job would fail. +
{/if}
@@ -1261,26 +1211,33 @@
{/if} {#if gitSyncSettings != undefined} - {#if $enterpriseLicense} -
- -
- {/if} + on:click={() => { + editWindmillGitSyncSettings() + console.log('Saving git sync settings', gitSyncSettings) + }}>Save git sync settings {!$enterpriseLicense ? '(ee only)' : ''} + + +
{#if Array.isArray(gitSyncSettings?.include_path)}

Filter on path + >Path filters Only scripts, flows and apps with their path matching one of those filters will be synced to the Git repositories below. The filters allow '*'' and '**' characters, with '*'' matching any character allowed in paths until the next slash (/) and @@ -1320,11 +1277,16 @@ Add filter

+
+ + Only new changes matching the filters will trigger a git sync. You still need to + initalize the repo to the desired state first. +

Filter on type + >Type filters On top of the filter path above, you can include only certain type of object to be synced with the Git repository.
By default everything is synced. @@ -1645,13 +1607,15 @@
- . For the git repo to be representative of the entire workspace, it is recommended to - set it up using the Windmill CLI before turning this option on. + For the git repo to be representative of the entire workspace, it is recommended to set + it up using the Windmill CLI before turning this option on.

Not familiar with Windmill CLI? - Check out the docs + Check out the docs

@@ -1662,9 +1626,11 @@
wmill workspace add  {$workspaceStore} {$workspaceStore} {`${$page.url.protocol}//${$page.url.hostname}/`}
-echo 'includes: ["f/**"]' > wmill.yaml
-wmill sync pull --raw --skip-variables --skip-secrets --skip-resources
+								>npm install -g windmill-cli
+wmill workspace add  {$workspaceStore} {$workspaceStore} {`${$page.url.protocol}//${$page.url.hostname}/`}
+wmill init
+# adjust wmill.yaml file configuraton as needed
+wmill sync pull
 git add -A
 git commit -m 'Initial commit'
 git pushFlow details

(undefined) const triggersCount = writable(undefined) - const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>( - 'webhooks' - ) + const selectedTriggerStore = writable< + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + >('webhooks') setContext('TriggerContext', { primarySchedule: primaryScheduleStore, selectedTrigger: selectedTriggerStore, diff --git a/frontend/src/routes/view_graph/+page.svelte b/frontend/src/routes/view_graph/+page.svelte index 6844733561..ef6bea27e0 100644 --- a/frontend/src/routes/view_graph/+page.svelte +++ b/frontend/src/routes/view_graph/+page.svelte @@ -8,7 +8,7 @@ : { modules: [], failureModule: undefined, preprocessorModule: undefined } - + openapi/openapi1.yaml -sed -z 's/ enum: \[script, failure, trigger, command, approval\]//' openapi/openapi1.yaml > openapi/openapi2.yaml -sed -z 's/ enum: \[deno, python3, go, bash\]//' openapi/openapi2.yaml > openapi/openapi3.yaml +# sed -i '/ enum:/ { N; / \[/ { N; /"S3Storage",/ { N; /"AzureBlobStorage",/ { N; /"AzureWorkloadIdentity",/ { N; /"S3AwsOidc",/ { N; / \]/d; }}}}}}' openapi/openapi.yaml +# sed -z 's/ extra_params:\n additionalProperties:\n type: string/ extra_params: {}/' openapi/openapi.yaml > openapi/openapi3.yaml +# sed -z 's/ enum: \[script, failure, trigger, command, approval\]//' openapi/openapi1.yaml > openapi/openapi2.yaml +# sed -z 's/ enum: \[deno, python3, go, bash\]//' openapi/openapi2.yaml > openapi/openapi3.yaml -npx @redocly/openapi-cli@latest bundle openapi/openapi3.yaml --ext json > openapi-bundled.json +npx @redocly/openapi-cli@latest bundle openapi/openapi.yaml --ext json > openapi-bundled.json -cat openapi-bundled.json | jq 'del(.paths."/oauth/list_connects")' > openapi-bundled-cleaned.json +cat openapi-bundled.json | jq 'del(.paths."/oauth/list_connects")' | jq 'del(.components.schemas.Job.discriminator)' > openapi-bundled-cleaned.json rm -rf api/ || true mkdir -p api +go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.4.1 ~/go/bin/oapi-codegen -old-config-style --package=windmill_api --generate=types,client openapi-bundled-cleaned.json > api/windmill_api.gen.go rm -rf openapi/ rm openapi* diff --git a/go-client/go.mod b/go-client/go.mod index 03833ac9a8..6cc15bf017 100644 --- a/go-client/go.mod +++ b/go-client/go.mod @@ -3,79 +3,16 @@ module github.com/windmill-labs/windmill-go-client go 1.19 require ( - github.com/deepmap/oapi-codegen v1.15.0 + github.com/google/uuid v1.5.0 + github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.8.4 ) require ( - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect - github.com/CloudyKit/jet/v6 v6.2.0 // indirect - github.com/Joker/jade v1.1.3 // indirect - github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aymerick/douceur v0.2.0 // indirect - github.com/bytedance/sonic v1.10.0-rc3 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/structs v1.1.0 // indirect - github.com/flosch/pongo2/v4 v4.0.2 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.9.1 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.1 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/gorilla/css v1.0.0 // indirect - github.com/iris-contrib/schema v0.0.6 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kataras/blocks v0.0.7 // indirect - github.com/kataras/golog v0.1.9 // indirect - github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 // indirect - github.com/kataras/pio v0.0.12 // indirect - github.com/kataras/sitemap v0.0.6 // indirect - github.com/kataras/tunnel v0.0.4 // indirect - github.com/klauspost/compress v1.16.7 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/labstack/echo/v4 v4.11.1 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mailgun/raymond/v2 v2.0.48 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/microcosm-cc/bluemonday v1.0.25 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/schollz/closestmatch v2.1.0+incompatible // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/tdewolff/minify/v2 v2.12.9 // indirect - github.com/tdewolff/parse/v2 v2.6.8 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yosssi/ace v0.0.5 // indirect - golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go-client/go.sum b/go-client/go.sum index 524578dca3..e856c9ce82 100644 --- a/go-client/go.sum +++ b/go-client/go.sum @@ -1,246 +1,29 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= -github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= -github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= -github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= -github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= -github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deepmap/oapi-codegen v1.15.0 h1:SQqViaeb4k2vMul8gx12oDOIadEtoRqTdLkxjzqtQ90= -github.com/deepmap/oapi-codegen v1.15.0/go.mod h1:a6KoHV7lMRwsPoEg2C6NDHiXYV3EQfiFocOlJ8dgJQE= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= -github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E= -github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= -github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go= -github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= -github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= -github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= -github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk= -github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY= -github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 h1:Vx8kDVhO2qepK8w44lBtp+RzN3ld743i+LYPzODJSpQ= -github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9/go.mod h1:ldkoR3iXABBeqlTibQ3MYaviA1oSlPvim6f55biwBh4= -github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w= -github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY= -github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= -github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= -github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= -github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= -github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= -github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= -github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= -github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= -github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= -github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= -github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= -github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0= -github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= -github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= -github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/go-client/windmill.go b/go-client/windmill.go index 7fde732aab..f8d46bf1d5 100644 --- a/go-client/windmill.go +++ b/go-client/windmill.go @@ -8,7 +8,6 @@ import ( "math/rand" "net/http" "os" - "github.com/google/uuid" api "github.com/windmill-labs/windmill-go-client/api" ) diff --git a/lsp/Dockerfile b/lsp/Dockerfile index be76edb450..2d56305116 100644 --- a/lsp/Dockerfile +++ b/lsp/Dockerfile @@ -26,7 +26,7 @@ ENV GOBIN=/usr/local/go/bin RUN /usr/local/go/bin/go install -v golang.org/x/tools/gopls@latest RUN pip3 install tornado python-lsp-jsonrpc ruff-lsp -COPY --from=denoland/deno:2.0.0 --chmod=755 /usr/bin/deno /usr/bin/deno +COPY --from=denoland/deno:2.0.2 --chmod=755 /usr/bin/deno /usr/bin/deno COPY Pipfile . diff --git a/lsp/Pipfile b/lsp/Pipfile index da5943c6c8..3ad5ebf804 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.409.2" -wmill_pg = ">=1.409.2" +wmill = ">=1.411.1" +wmill_pg = ">=1.411.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 99d06495f5..6ce153606e 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.409.2 + version: 1.411.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index b87ef55862..6585bf264d 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. -ModuleVersion = '1.409.2' +ModuleVersion = '1.411.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index ad297dbb2a..6a51dae631 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.409.2" +version = "1.411.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 4be054a1b2..8bb05b8084 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.409.2" +version = "1.411.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index bcba866e9c..4d617e7f3b 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.409.2", + "version": "1.411.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index de0a1c198e..bab741796c 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.409.2", + "version": "1.411.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 09997b8366..d8999709d6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.409.2 +1.411.1