Compare commits

..

74 Commits

Author SHA1 Message Date
Ruben Fiszel
45bf59ba32 chore(main): release 1.409.2 (#4541)
* chore(main): release 1.409.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-17 01:22:18 +02:00
Ruben Fiszel
1466da3999 fix: add extra args support for exception to bun scripts 2024-10-17 01:19:25 +02:00
Ruben Fiszel
3174024d8e fix: fix script persistence in url + add support for extra error args in python 2024-10-17 01:07:56 +02:00
Ruben Fiszel
4e6c0d1e80 chore(main): release 1.409.1 (#4540)
* chore(main): release 1.409.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-17 00:08:06 +02:00
Ruben Fiszel
6abb346013 fix(git-sync): propagate update of folders with git sync 2024-10-16 23:44:56 +02:00
Ruben Fiszel
79100f8d0a move to lto thin 2024-10-16 23:39:51 +02:00
Ruben Fiszel
28f6f6b067 chore: update bun to 1.30.0 2024-10-16 22:53:42 +02:00
Ruben Fiszel
d24e153065 fix(apidocs): fix generated openapi files 2024-10-16 22:11:56 +02:00
Ruben Fiszel
8df2cec40a chore(main): release 1.409.0 (#4530)
* chore(main): release 1.409.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-16 20:49:06 +02:00
Ruben Fiszel
a8c4ea2334 fix(python-client): improve get_job_status for running jobs 2024-10-16 20:09:20 +02:00
Ruben Fiszel
20e8be15fe add lto fat to release build 2024-10-16 19:51:08 +02:00
Ruben Fiszel
c3b536b1b8 fix(app): make s3 uploads persistent across tabs change 2024-10-16 19:43:20 +02:00
wendrul
c06c42a1ed Win 465 index observability 1 (#4538)
* Factor isJobCancellable function in a utils file

* Add message for last indexed job and warning of deployement

* Prepare sqlx

* Update ee repo ref
2024-10-16 19:25:50 +02:00
Ruben Fiszel
01b95ebd8d nitso utline triggers 2024-10-16 19:23:16 +02:00
Ruben Fiszel
99814c7ab0 nits schedule UX 2024-10-16 19:19:24 +02:00
Ruben Fiszel
10414df4a7 fix email token generation 2024-10-16 18:23:06 +02:00
Faton Ramadani
91a3d06529 feat(frontend): unify all triggers UX and simplify flow settings (#4259)
* feat(frontend): added list of triggers in the flow graph

* feat(frontend): added list of triggers in the flow graph

* feat(frontend): clean up

* feat(frontend): improve UX

* feat(frontend): triggers

* feat(frontend): triggers

* feat(frontend): done

* feat(frontend): fix trigger when position when a preprocessor is presetn

* Glm/rework flow settings v2 (#4497)

* fat(frontend): simplify flow settings menu

* improve scroll

* changing mute toggle

* Add advanced settings badge

* Add nord theme colors

* Add bage for advanced options

* fix minor issue

* fix minor issue

* Add triggers menu to flow settings

* Add quick trigger access

* remove triggers in flow settings

* fix minor issue

* Move triggers settings to flow right panel

* polishing

* fix unset store

* remove save up to for triggers

* fix padding

* reset default tag color

* remove custom select component

* revert path change

* revert section modif

* Revert unused feature

---------

Co-authored-by: Guilhem <guilhem@mbp-de-windmill.home>

* Connect top bar cron to schedules settings

* Turn copilot into node

* fix copilot placement

* remove useless import

* fix center copilot

* fix binding

* remove copilot on top of preprocessor

* render copilot node on condition

* quickfix

* remove copilot node

* fix minor issues

* fix route count update

* fix schedule sync

* harmonize colors

* fix alignment and add edges

* recenter node summary

* fix schedules sync

* Add id title

* all

* all

* all

* iteration

* all

* all

* done

* fix

* more fixes

---------

Co-authored-by: Guilhem <guilhemlemouel@gmail.com>
Co-authored-by: Guilhem <guilhem@mbp-de-windmill.home>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2024-10-16 17:58:15 +02:00
Alexander Petric
f38b3d14e8 fix(ui): dark mode support for queue metrics based critical alert (#4535) 2024-10-16 16:26:07 +02:00
wendrul
e736572db1 fix: canceled jobs button reporting 0 jobs cancelled (#4534) 2024-10-16 16:15:25 +02:00
wendrul
7126ba12c7 feat: Scroll to element in virtual list when clicking on graph point (#4532)
* Scroll to element in virtual list when clicking on graph point

* Remove debug statements

* Remove reactive statement in favor of eventful triggering
2024-10-16 14:37:20 +02:00
Alexander Petric
29cb954639 rhel9 build workflow (#4528)
* dockerfile for rhel9

* attach rh to release page workflow

* changing name

* updating new workflow

* updating new workflow

* pushing new workflow

* trigger workflow

* trigger workflow

* trigger workflow

* trigger workflow

* finished workflow

* ellipsis comment
2024-10-16 14:36:36 +02:00
wendrul
411bce7e13 fix: Add indexer backup lock to fit the deployment model (#4531)
* Pass db to init_index

* Prepare sqlx

* Update ee repo ref
2024-10-16 14:36:12 +02:00
Alexander Petric
99c5b3ecda feat(sso): adding the ability to define a custom display name for sso (#4529)
* feat(sso): adding the ability to define a custom display name for sso

* adding openapi-deref.json

* make the display_name field optional

* Update ee-repo-ref.txt
2024-10-15 17:32:39 +02:00
Ruben Fiszel
9eb1ecc9f3 fix(app): accept connecting to non yet existing state output for convenience 2024-10-15 09:47:22 +02:00
Ruben Fiszel
dcdbf1afb4 fix(app): improve absolute url handling in download button and downloadFile 2024-10-15 09:18:24 +02:00
Ruben Fiszel
34c39ce0ab nit closing flowgraph 2024-10-13 01:30:01 +02:00
Ruben Fiszel
288dfe7c70 custom windmill icon2 2024-10-13 00:47:10 +02:00
Ruben Fiszel
8867260e43 display version in backend tests 2024-10-13 00:03:36 +02:00
Ruben Fiszel
4112eb6072 backend tests 2024-10-12 23:33:09 +02:00
Ruben Fiszel
9d2cb91f9c chore(main): release 1.408.1 (#4526)
* chore(main): release 1.408.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-12 18:03:06 +02:00
Ruben Fiszel
a323d2b29a fix deno lockall tests 2024-10-12 17:59:05 +02:00
Ruben Fiszel
fa642c8db1 fix deno lockall tests 2024-10-12 17:57:28 +02:00
Ruben Fiszel
42fe31f804 fix: fix deno cache --allow-import on deno 2 2024-10-12 17:18:46 +02:00
Ruben Fiszel
242a251ab1 use push isolation level to isolated in more push cases 2024-10-12 17:00:54 +02:00
Ruben Fiszel
7a92a152d1 use isolated transctions for workflow_as_code 2024-10-12 16:23:53 +02:00
Ruben Fiszel
fad4785b0a verify vulnerability only on releases + tag latest on full + cuda 2024-10-12 15:39:13 +02:00
Ruben Fiszel
296aa97bd8 add trace_id to all request spans 2024-10-12 15:19:43 +02:00
Ruben Fiszel
d77412cef6 add more tracing on cloud to workflow_as_code 2024-10-12 14:11:21 +02:00
Ruben Fiszel
685eb5d471 support skip_update 2024-10-12 13:39:59 +02:00
Ruben Fiszel
fcdf82af36 chore(main): release 1.408.0 (#4522)
* chore(main): release 1.408.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-12 04:38:44 +02:00
Ruben Fiszel
d2c4d3fa20 fix: improve workflow as code row-lock on db to handle more concurrency 2024-10-12 03:39:43 +02:00
Ruben Fiszel
e06c845ed4 cli yaml parsing error more verbose 2024-10-12 03:01:54 +02:00
Alexander Petric
f82f091290 feat(app builder): file download helper (#4511)
* feat(app builder): file download helper

* surface error via toast

* save

* handle s3 object
2024-10-12 00:39:31 +02:00
Ruben Fiszel
9f7edbaf1a make cli more reliable 2024-10-11 16:33:18 +02:00
Ruben Fiszel
2dab068fd3 nit FileDownload 2024-10-11 16:04:25 +02:00
Ruben Fiszel
7e754a1e50 improve handling of toString arg in schema 2024-10-11 15:55:02 +02:00
Ruben Fiszel
be112408e7 fix(frontend): set unused schema property fields to null 2024-10-11 15:25:32 +02:00
Ruben Fiszel
568cc66932 fix(cli): handle case where 'toString' is a schema field 2024-10-11 12:28:40 +02:00
Ruben Fiszel
0915968eba allow python multiprocessing on nsjail 2024-10-11 01:40:09 +02:00
Ruben Fiszel
982dde2b9d fix(frontend): s3 file uploader works on public apps too 2024-10-11 01:08:40 +02:00
Ruben Fiszel
7db74eecb9 add --allow-import to nsjail deno 2024-10-10 15:29:49 +02:00
Ruben Fiszel
81b0ff1ad1 chore(main): release 1.407.2 (#4518)
* chore(main): release 1.407.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-10 15:14:15 +02:00
Ruben Fiszel
b133f19333 nit deno_core 2024-10-10 15:13:55 +02:00
Ruben Fiszel
d9bdc5a5b0 fix: improve default properties of new nodes of flows (suspend, branchone, branchall) 2024-10-10 15:00:46 +02:00
Ruben Fiszel
ba17dfc841 chore(main): release 1.407.1 (#4515)
* chore(main): release 1.407.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-10 04:42:55 +02:00
Ruben Fiszel
7ca5bf2fae fix: improve handling of empty lock files on deno 2.0 2024-10-10 04:39:57 +02:00
Ruben Fiszel
cf8d443603 chore(main): release 1.407.0 (#4513)
* chore(main): release 1.407.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-10 04:24:17 +02:00
Ruben Fiszel
0e61f8db78 deno 2.0 use frozen for cache 2024-10-10 04:06:27 +02:00
Ruben Fiszel
c3a5736641 fix: update internal deno runtime to latest (deno 2.0) 2024-10-10 03:42:28 +02:00
Ruben Fiszel
26b11a0015 feat: upgrade to deno 2 2024-10-10 02:30:42 +02:00
Ruben Fiszel
7dbe49b77b improve init script assignment 2024-10-10 02:25:25 +02:00
Ruben Fiszel
2e9312216e chore(main): release 1.406.0 (#4509)
* chore(main): release 1.406.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-10-10 00:18:49 +02:00
Ruben Fiszel
7b0503332d fix: remove monaco-editor for app preview code path for faster app loads 2024-10-10 00:04:25 +02:00
Ruben Fiszel
25e417538f fix tests 2024-10-09 22:37:07 +02:00
Ruben Fiszel
6280ed3524 fix s3 bucket test 2024-10-09 21:30:28 +02:00
Ruben Fiszel
ffaf756e9a sqlx 2024-10-09 21:17:01 +02:00
Ruben Fiszel
21cd7a63ae fix no resource picker 2024-10-09 21:00:30 +02:00
Alexander Petric
d90d6c2b89 feat(monitoring): Critical Alerts for Jobs Waiting in Queue [enterprise] (#4491)
* adding the job queue monitor

* first draft of queue metrics ui

* fix svelte check complaints

* harmonizing naming

* adding concurrency_lock table

* updating ee repo ref

* Rename 20241008155800_alert_lock_table.up.sql to 20241008155800_concurrency_lock_table.up.sql

* Rename 20241008155800_alert_lock_table.down.sql to 20241008155800_concurrency_lock_table.down.sql

* updating ee.rs

* adding owner as optional column

* handling of ee/non-ee

* Update ee.rs with newline
2024-10-09 20:48:18 +02:00
Faton Ramadani
111bfc6a65 feat(frontend): components can be moved inside containers by holding ctrl/cmd
* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): move component inside containers

* feat(frontend): done

* feat(frontend): done

* fix(frontend): wip

* fix(frontend): wip

* fix(frontend): done

* fix(frontend): done

* fix(frontend): fix anchored

* fix(frontend): restore event

* feat(frontend): support dropping on the top level + hide the shadow when leaving the grid

* feat(frontend): drop in the correct subgrid when there are multiple

* feat(frontend): wip

* feat(frontend): fix hide shadow

* feat(frontend): remove dead code

* feat(frontend): wip

* feat(frontend): fix overflow

* feat(frontend): wip

* feat(frontend): done

* feat(frontend): fix z-index

* feat(frontend): wip

* feat(frontend): fix shadow

* feat(frontend): fix resize + drop position

* feat(frontend): fix dropped when no movement

* feat(frontend): hide the fake shadow if the move mode is set

* feat(frontend): add small timeout when we leave the insert mode

* feat(frontend): render the shadow where the component will drop

* feat(frontend): done

* feat(frontend): done

* feat(frontend): improve perf

* feat(frontend): done

* feat(frontend): remove console.log

* feat(frontend): fix move toplevel

* feat(frontend): Components positions are restored when the mode changes

* feat(frontend): Components positions are restored when the mode changes

* feat(frontend): Components positions are restored when the mode changes

* feat(frontend): fix edges cases + improve perf

* feat(frontend): fix drop position

* feat(frontend): fix final drop position

* feat(frontend): fix final drop position

* feat(frontend): fix insert from component bar

* feat(frontend): Fix disapearing components
2024-10-09 13:08:16 +02:00
Ruben Fiszel
257f0971f8 fix(cli): instance sync push does not require sync pull 2024-10-09 12:58:49 +02:00
Alexander Petric
f831b9b1fe improving pwsh error handling (#4504)
* making pwsh error handling more robust

* cargo fmt
2024-10-09 05:20:46 +02:00
Ruben Fiszel
b579db61ec allow more customisation of frontend (#4508)
* all

* npm check
2024-10-09 04:34:54 +02:00
HugoCasa
e44decb9c2 BYPASSRLS admin policy falllback (#4493)
* BYPASSRLS admin policy falllback

* use fixed list instead

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-10-08 20:08:29 +02:00
Guilhem
644dd87c50 fix icon alignment in add App button (#4496)
Co-authored-by: Guilhem <guilhem@mbp-de-windmill.home>
2024-10-07 21:20:28 +02:00
263 changed files with 13743 additions and 4363 deletions

View File

@@ -1,22 +1,6 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.80-slim-bookworm
ARG PYTHON_IMAGE=python:3.11.4-slim-bookworm
FROM ${DEBIAN_IMAGE} as downloader
ARG TARGETPLATFORM
SHELL ["/bin/bash", "-c"]
RUN apt update -y
RUN apt install -y unzip curl
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.46.3/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.46.3/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true
RUN unzip deno.zip && rm deno.zip
FROM ${RUST_IMAGE} as builder
@@ -31,7 +15,7 @@ ENV SQLX_OFFLINE=true
RUN mkdir -p /frontend/build
RUN apt-get update \
&& apt-get install -y ca-certificates tzdata libpq5 cmake\
&& apt-get install -y ca-certificates tzdata libpq5 cmake unzip\
make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libxml2-dev \
libxmlsec1-dev libffi-dev liblzma-dev mecab-ipadic-utf8 libgdbm-dev libc6-dev git libprotobuf-dev libnl-route-3-dev \
@@ -56,13 +40,14 @@ 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.27 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.1.30 /usr/local/bin/bun /usr/bin/bun
ARG TARGETPLATFORM
RUN [ "$TARGETPLATFORM" == "linux/amd64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.41.0/deno-x86_64-unknown-linux-gnu.zip -o deno.zip || true
RUN [ "$TARGETPLATFORM" == "linux/arm64" ] && curl -Lsf https://github.com/denoland/deno/releases/download/v1.41.0/deno-aarch64-unknown-linux-gnu.zip -o deno.zip || true
RUN curl -Lsf https://github.com/denoland/deno/releases/download/v2.0.0/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
COPY --from=downloader --chmod=755 /deno /usr/bin/deno
RUN unzip deno.zip && rm deno.zip && mv deno /usr/bin/deno
RUN apt-get update \
&& apt-get install -y postgresql-client --allow-unauthenticated

View File

@@ -41,6 +41,10 @@ jobs:
- name: cargo test
timeout-minutes: 15
run:
/usr/bin/deno --version &&
/usr/bin/bun -v &&
go version &&
/usr/local/bin/python3 --version &&
mkdir frontend/build && cd backend && touch
windmill-api/openapi-deref.yaml &&
DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill

View File

@@ -0,0 +1,128 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
name: Build and publish windmill for RHEL9
on:
workflow_dispatch
permissions: write-all
jobs:
build_ee:
runs-on: ubicloud
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ env.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
- uses: depot/setup-action@v1
- name: Docker meta
id: meta-ee-public
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-rhel9
flavor: |
latest=false
tags: |
type=sha
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Substitute EE code
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Copy RHEL9 Dockerfile
run: |
cp ./docker/RHEL9/Dockerfile ./Dockerfile
- name: Build and push publicly ee amd64
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
tags: |
${{ steps.meta-ee-public.outputs.tags }}-amd64
labels: |
${{ steps.meta-ee-public.outputs.labels }}-amd64
org.opencontainers.image.licenses=Windmill-Enterprise-License
- name: Build and push publicly ee arm64
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
tags: |
${{ steps.meta-ee-public.outputs.tags }}-arm64
labels: |
${{ steps.meta-ee-public.outputs.labels }}-arm64
org.opencontainers.image.licenses=Windmill-Enterprise-License
- uses: shrink/actions-docker-extract@v3
id: extract-ee-amd64
with:
image: ${{ steps.meta-ee-public.outputs.tags}}-amd64
path: "/windmill/target/release/windmill"
- uses: shrink/actions-docker-extract@v3
id: extract-ee-arm64
with:
image: ${{ steps.meta-ee-public.outputs.tags}}-arm64
path: "/windmill/target/release/windmill"
- name: Rename binary with corresponding architecture
run: |
mv "${{ steps.extract-ee-amd64.outputs.destination }}/windmill" "${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel9"
mv "${{ steps.extract-ee-arm64.outputs.destination }}/windmill" "${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel9"
- uses: actions/upload-artifact@v4
with:
name: RHEL9-amd64 build
path: ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel9
- uses: actions/upload-artifact@v4
with:
name: RHEL9-arm64 build
path: ${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel9
# - name: Attach binary to release
# uses: softprops/action-gh-release@v2
# if: startsWith(github.ref, 'refs/tags/')
# with:
# files: |
# ${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel9
# ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel9

View File

@@ -394,7 +394,7 @@ jobs:
verify_ee_image_vulnerabilities:
runs-on: ubicloud
needs: [tag_latest_ee]
# if: ${{ startsWith(github.ref, 'refs/tags/') }}
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -589,8 +589,6 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-cuda
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -635,8 +633,6 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-slim
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -680,8 +676,6 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-slim
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -726,8 +720,6 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-full
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -771,8 +763,6 @@ jobs:
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-full
flavor: |
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

View File

@@ -1,5 +1,103 @@
# Changelog
## [1.409.2](https://github.com/windmill-labs/windmill/compare/v1.409.1...v1.409.2) (2024-10-16)
### Bug Fixes
* add extra args support for exception to bun scripts ([1466da3](https://github.com/windmill-labs/windmill/commit/1466da3999add0238b9c42ca13df52194f082fc0))
* fix script persistence in url + add support for extra error args in python ([3174024](https://github.com/windmill-labs/windmill/commit/3174024d8e6ecbe9f8c9e1ea055d611f652c0057))
## [1.409.1](https://github.com/windmill-labs/windmill/compare/v1.409.0...v1.409.1) (2024-10-16)
### Bug Fixes
* **apidocs:** fix generated openapi files ([d24e153](https://github.com/windmill-labs/windmill/commit/d24e1530655d27ffda9bb4c19471dc1431124cc2))
* **git-sync:** propagate update of folders with git sync ([6abb346](https://github.com/windmill-labs/windmill/commit/6abb346013da4a907a860713a8a67642985b8025))
## [1.409.0](https://github.com/windmill-labs/windmill/compare/v1.408.1...v1.409.0) (2024-10-16)
### Features
* **frontend:** unify all triggers UX and simplify flow settings ([#4259](https://github.com/windmill-labs/windmill/issues/4259)) ([91a3d06](https://github.com/windmill-labs/windmill/commit/91a3d065298cce7a882464fa0cd31d8f1ae9dda2))
* Scroll to element in virtual list when clicking on graph point ([#4532](https://github.com/windmill-labs/windmill/issues/4532)) ([7126ba1](https://github.com/windmill-labs/windmill/commit/7126ba12c7eb52d2cfbe8d83311b5592a5707bce))
* **sso:** adding the ability to define a custom display name for sso ([#4529](https://github.com/windmill-labs/windmill/issues/4529)) ([99c5b3e](https://github.com/windmill-labs/windmill/commit/99c5b3ecdacb1158c2cba5b891c4c3b8b70c3b6a))
### Bug Fixes
* Add indexer backup lock to fit the deployment model ([#4531](https://github.com/windmill-labs/windmill/issues/4531)) ([411bce7](https://github.com/windmill-labs/windmill/commit/411bce7e13aabfa53db80d55261ea4511f6d1ae9))
* **app:** accept connecting to non yet existing state output for convenience ([9eb1ecc](https://github.com/windmill-labs/windmill/commit/9eb1ecc9f3017e2f4284a827a2bcd21f36b1b8ac))
* **app:** improve absolute url handling in download button and downloadFile ([dcdbf1a](https://github.com/windmill-labs/windmill/commit/dcdbf1afb4d5a18e00b9bbb1eb0bef129ea5667f))
* **app:** make s3 uploads persistent across tabs change ([c3b536b](https://github.com/windmill-labs/windmill/commit/c3b536b1b8069898131768867a187b186b21e537))
* canceled jobs button reporting 0 jobs cancelled ([#4534](https://github.com/windmill-labs/windmill/issues/4534)) ([e736572](https://github.com/windmill-labs/windmill/commit/e736572db10929ae5e123c4f6cef73b8e90fc29b))
* **python-client:** improve get_job_status for running jobs ([a8c4ea2](https://github.com/windmill-labs/windmill/commit/a8c4ea2334d2535fa7d5d43f58d65565afe8f3e5))
* **ui:** dark mode support for queue metrics based critical alert ([#4535](https://github.com/windmill-labs/windmill/issues/4535)) ([f38b3d1](https://github.com/windmill-labs/windmill/commit/f38b3d14e8092ae58817511aea91a4e77725ead6))
## [1.408.1](https://github.com/windmill-labs/windmill/compare/v1.408.0...v1.408.1) (2024-10-12)
### Bug Fixes
* fix deno cache --allow-import on deno 2 ([42fe31f](https://github.com/windmill-labs/windmill/commit/42fe31f804c9e6643cd90167494e45270831e013))
## [1.408.0](https://github.com/windmill-labs/windmill/compare/v1.407.2...v1.408.0) (2024-10-12)
### Features
* **app builder:** file download helper ([#4511](https://github.com/windmill-labs/windmill/issues/4511)) ([f82f091](https://github.com/windmill-labs/windmill/commit/f82f09129096cfff975370d8bb7b6d832a2b8f9f))
### Bug Fixes
* **cli:** handle case where 'toString' is a schema field ([568cc66](https://github.com/windmill-labs/windmill/commit/568cc66932fb0470f5e89de7b02d94dba4050638))
* **frontend:** s3 file uploader works on public apps too ([982dde2](https://github.com/windmill-labs/windmill/commit/982dde2b9dfe6d9eda300c683af729d97a03cb4d))
* **frontend:** set unused schema property fields to null ([be11240](https://github.com/windmill-labs/windmill/commit/be112408e7c4601314726e9517c37daaeaa1bf09))
* improve workflow as code row-lock on db to handle more concurrency ([d2c4d3f](https://github.com/windmill-labs/windmill/commit/d2c4d3fa207379cb0b8ac180f6ccc759045580e8))
## [1.407.2](https://github.com/windmill-labs/windmill/compare/v1.407.1...v1.407.2) (2024-10-10)
### Bug Fixes
* improve default properties of new nodes of flows (suspend, branchone, branchall) ([d9bdc5a](https://github.com/windmill-labs/windmill/commit/d9bdc5a5b08dd4d0381304656af097315398c9d4))
## [1.407.1](https://github.com/windmill-labs/windmill/compare/v1.407.0...v1.407.1) (2024-10-10)
### Bug Fixes
* improve handling of empty lock files on deno 2.0 ([7ca5bf2](https://github.com/windmill-labs/windmill/commit/7ca5bf2faeff44a7543b1afa9369c140fcb71dfc))
## [1.407.0](https://github.com/windmill-labs/windmill/compare/v1.406.0...v1.407.0) (2024-10-10)
### Features
* upgrade to deno 2 ([26b11a0](https://github.com/windmill-labs/windmill/commit/26b11a00150acbe101abe4bb542f24379da0cc56))
### Bug Fixes
* update internal deno runtime to latest (deno 2.0) ([c3a5736](https://github.com/windmill-labs/windmill/commit/c3a57366419882ea2de1938bea592c795b1a1d03))
## [1.406.0](https://github.com/windmill-labs/windmill/compare/v1.405.5...v1.406.0) (2024-10-09)
### Features
* **frontend:** components can be moved inside containers by holding ctrl/cmd ([111bfc6](https://github.com/windmill-labs/windmill/commit/111bfc6a659037ae7029e8f557256e2fffcf979b))
* **monitoring:** Critical Alerts for Jobs Waiting in Queue [enterprise] ([#4491](https://github.com/windmill-labs/windmill/issues/4491)) ([d90d6c2](https://github.com/windmill-labs/windmill/commit/d90d6c2b896c5f99e00681656f376b180901f272))
### Bug Fixes
* **cli:** instance sync push does not require sync pull ([257f097](https://github.com/windmill-labs/windmill/commit/257f0971f86938da71b879f32d93473976eaa920))
* remove monaco-editor for app preview code path for faster app loads ([7b05033](https://github.com/windmill-labs/windmill/commit/7b0503332d1bdd7f5999a5ef99150f8c9f6f18be))
## [1.405.5](https://github.com/windmill-labs/windmill/compare/v1.405.4...v1.405.5) (2024-10-04)

View File

@@ -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:1.46.3 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=denoland/deno:2.0.0 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun
COPY --from=oven/bun:1.1.30 /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

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM healthchecks WHERE check_type = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "0ee63ef2dd5c88edba2a1f56d31f29876724922f148dad7af35b36efbf70207a"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO concurrency_locks (id, last_locked_at, owner)\n VALUES ($1, now(), $2)\n ON CONFLICT (id)\n DO UPDATE SET\n last_locked_at = now(),\n owner = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM schedule 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": "1ca5bc2d35c0498b587fd0618434def64233dc4f8fc3344d8d74be8e96ded659"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM healthchecks WHERE healthy = true AND created_at < NOW() - INTERVAL '14 days'",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "2041526bc58872d71f91f7698144039bd67f8e37895befa94a15b7e4019e114b"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO healthchecks (check_type, healthy) VALUES ($1, false)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "27920aaa55666ffc14a36a247f89ff7994ee40d3953b9f772d0e0ab999bccb7b"
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM http_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": "31bc3dcea29be9cc0242771d25a232f173446d29c08fc29ddb8d55294f2c070e"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT created_at FROM healthchecks WHERE check_type = $1 ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "34a45763bb4d14162f4cd3fa07cd8020f1f6085f4ee85f5eab3458637edf26cd"
}

View File

@@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT COUNT(*) as count, \n MIN(scheduled_for) as oldest_job\n FROM queue \n WHERE tag = $1 \n AND scheduled_for <= NOW() - $2::interval \n AND running = false\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "oldest_job",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Interval"
]
},
"nullable": [
null,
null
]
},
"hash": "3ecb25b05d6c14b499f9b00af42ae74134728899f6b59c68b246979bc5143e30"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE concurrency_locks SET\n last_locked_at = now()\n WHERE id = $1 AND owner = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "57e270e032e8c04dda7b5c1ca949861756b3ad367a4a500728332a7cb91560a4"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "5fc6b4a4dbb7875bdec76f876c18543435a95b019b20081f52f6ed6f4457e3c7"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT owner FROM concurrency_locks WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "owner",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "5fd70c70ce52cbc51fa9124cb05f82b5951f17d1b7eade53c6d89253d55f8b9f"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO windmill_migrations (name) VALUES ('bypassrls_1-2')",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO concurrency_locks (id, last_locked_at) VALUES ($1, NOW()) ON CONFLICT (id) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": []
},
"hash": "900ac59515e4283f4b57516210575dfe92f74a7220ed69e61899a6e0f053d9cd"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM token WHERE label LIKE 'email-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "a7f5431e3b8960e9dc46fae69dd4391516d8b169186548ec44528c84078b80d8"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE healthchecks SET healthy = true WHERE check_type = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "ad42118ccf6a9d2d1e072c4df064ddf964a5b3cd088fc162d0d8222325d4a5ea"
}

View File

@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
true,
false
false,
true
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"

View File

@@ -52,7 +52,8 @@
"trigger",
"failure",
"command",
"approval"
"approval",
"preprocessor"
]
}
}

View File

@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "SELECT schedule FROM schedule WHERE path = $1 AND script_path = $1 AND is_flow = $2 AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "schedule",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
false
]
},
"hash": "c060b8bbc5af7d2e7d0aaff64f0f62ec9db58611a99b0ba7f0375638b128ab89"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE concurrency_locks SET last_locked_at = NOW() WHERE id = $1 AND last_locked_at < NOW() - INTERVAL '1 second' * $2 RETURNING 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Float8"
]
},
"nullable": [
null
]
},
"hash": "c4e1873bfc7b905e7299a021f4baa2a97e95f4797c5e11f37822e19828422b7e"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO token\n (token, email, label, expiration, super_admin, scopes)\n VALUES ($1, $2, $3, $4, $5, $6)",
"query": "INSERT INTO token\n (token, email, label, expiration, super_admin, scopes, workspace_id)\n VALUES ($1, $2, $3, $4, $5, $6, $7)",
"describe": {
"columns": [],
"parameters": {
@@ -10,10 +10,11 @@
"Varchar",
"Timestamptz",
"Bool",
"TextArray"
"TextArray",
"Varchar"
]
},
"nullable": []
},
"hash": "34ad8a2a5bd89b9b8e25847a7e5e94ef99e35a178ad6328c1bcde2a6d6f88cb5"
"hash": "c624f15f3e321b1eecf123da9bf0b18e8c1d16ef25ffb9d04e5447d0d583d55c"
}

View File

@@ -0,0 +1,59 @@
{
"db_name": "PostgreSQL",
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes, email FROM token WHERE workspace_id = $1 AND scopes @> ARRAY['run:script/' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "label",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "token_prefix",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "expiration",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "last_used_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "scopes",
"type_info": "TextArray"
},
{
"ordinal": 6,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true,
null,
true,
false,
false,
true,
true
]
},
"hash": "c7ee7ce64686cef41cebd99ad7ef31572fc1bf12e6ae473fd58fafb025989965"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT last_locked_at\n FROM concurrency_locks\n WHERE id = $1\n FOR UPDATE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "last_locked_at",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "cecf1addc4aecb087a14786b2a9165895ca61ef042947c7314f66514d7f29edc"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "d7a0f19f9e18d2ea49316012375ad78b69292ba091d69880945e42bebe890d66"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'bypassrls_1-2')",
"query": "SELECT EXISTS(SELECT 1 FROM healthchecks WHERE check_type = $1 AND healthy = false)",
"describe": {
"columns": [
{
@@ -10,11 +10,13 @@
}
],
"parameters": {
"Left": []
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "eb1f916f9beea3eea83ce359f5305d0cfb0d6cdba9cc56c6139f57e46345f843"
"hash": "eb932b613a6dbb2cdff97e5512d42b538ba83115c0ea798be00b01659600f45a"
}

View File

@@ -0,0 +1,59 @@
{
"db_name": "PostgreSQL",
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes, email FROM token WHERE workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "label",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "token_prefix",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "expiration",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "last_used_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "scopes",
"type_info": "TextArray"
},
{
"ordinal": 6,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true,
null,
true,
false,
false,
true,
true
]
},
"hash": "eff32aeac25a75d06f73e08c26dd3fd25f6b85cbea870505751c6a82457ae1da"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM token WHERE label LIKE 'email-%' AND workspace_id = $1 AND scopes @> ARRAY['run:script/' || $2]::text[]",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "f06e0e4fa358b26792df22fff48b71a6fcfa1e5603ea472892917c1accd1aafb"
}

742
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.405.5"
version = "1.409.2"
authors.workspace = true
edition.workspace = true
@@ -27,7 +27,7 @@ members = [
]
[workspace.package]
version = "1.405.5"
version = "1.409.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -39,6 +39,9 @@ path = "./src/main.rs"
opt-level = 0
incremental = true
[profile.release]
lto = "thin"
[features]
default = []
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-indexer/enterprise"]
@@ -172,20 +175,24 @@ tokio-util = { version = "^0", features = ["io"] }
json-pointer = "^0"
itertools = "^0"
regex = "^1"
deno_fetch = "0.187.0"
deno_tls = "0.150.0"
deno_console = "0.163.0"
deno_url = "0.163.0"
deno_webidl = "0.163.0"
deno_web = "0.194.0"
deno_net = "0.155.0"
deno_core = "0.299.0"
deno_ast = { version = "=0.40.0", features = ["transpiling"] }
deno_fetch = "0.195.0"
deno_tls = "0.158.0"
deno_console = "0.171.0"
deno_url = "0.171.0"
deno_webidl = "0.171.0"
deno_web = "0.202.0"
deno_net = "0.163.0"
deno_core = "0.311.0"
deno_ast = { version = "=0.42.2", features = ["transpiling"] }
swc_common = "=0.37.5"
swc_ecma_parser = "=0.149.1"
swc_ecma_ast = "=0.118.2"
swc_ecma_visit = "=0.104.8"
async-recursion = "^1"
swc_common = "=0.33.26"
swc_ecma_parser = "=0.144.3"
swc_ecma_ast = "=0.113.7"
swc_ecma_visit = "=0.99.1"
base64 = "0.21.0"
base32 = "^0"
hmac = "0.12.1"
@@ -270,8 +277,7 @@ tikv-jemallocator = { version = "0.5" }
tikv-jemalloc-sys = { version = "^0.5" }
tikv-jemalloc-ctl = { version = "^0.5" }
# 0.1.12 broken (nested dependency of swc_common)
triomphe = "<0.1.12"
triomphe = "^0"
tantivy = "0.22.0"

View File

@@ -1,14 +0,0 @@
CREATE POLICY admin_policy ON account TO windmill_admin USING (true);
CREATE POLICY admin_policy ON app TO windmill_admin USING (true);
CREATE POLICY admin_policy ON audit TO windmill_admin USING (true);
CREATE POLICY admin_policy ON capture TO windmill_admin USING (true);
CREATE POLICY admin_policy ON completed_job TO windmill_admin USING (true);
CREATE POLICY admin_policy ON flow TO windmill_admin USING (true);
CREATE POLICY admin_policy ON folder TO windmill_admin USING (true);
CREATE POLICY admin_policy ON queue TO windmill_admin USING (true);
CREATE POLICY admin_policy ON raw_app TO windmill_admin USING (true);
CREATE POLICY admin_policy ON resource TO windmill_admin USING (true);
CREATE POLICY admin_policy ON schedule TO windmill_admin USING (true);
CREATE POLICY admin_policy ON script TO windmill_admin USING (true);
CREATE POLICY admin_policy ON usr_to_group TO windmill_admin USING (true);
CREATE POLICY admin_policy ON variable TO windmill_admin USING (true);

View File

@@ -1 +1 @@
0f5f42d2f8f5f1af05c8086f3dc7ad38d83750df
0428068e4fbbd1380a4d8bbaab5c8e7955decdb8

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,24 @@
-- Add up migration script here
DO
$$
DECLARE
tbl_name text;
policy_exists boolean;
tbl_names text[] := ARRAY['account', 'app', 'audit', 'capture', 'completed_job', 'flow', 'folder', 'http_trigger', 'queue', 'raw_app', 'resource', 'schedule', 'script', 'usr_to_group', 'variable'];
BEGIN
FOR tbl_name IN SELECT unnest(tbl_names)
LOOP
SELECT EXISTS (
SELECT 1
FROM pg_policies
WHERE schemaname = 'public'
AND tablename = tbl_name
AND policyname = 'admin_policy'
) INTO policy_exists;
IF NOT policy_exists THEN
EXECUTE format('CREATE POLICY admin_policy ON %I TO windmill_admin USING (true);', tbl_name);
END IF;
END LOOP;
END;
$$;

View File

@@ -0,0 +1,2 @@
-- Drop the alert_locks table
DROP TABLE IF EXISTS concurrency_locks;

View File

@@ -0,0 +1,6 @@
-- Create the alert_locks table
CREATE TABLE concurrency_locks (
id VARCHAR PRIMARY KEY,
last_locked_at TIMESTAMP NOT NULL,
owner VARCHAR NULL
);

View File

@@ -15,13 +15,13 @@ use windmill_parser::{
use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Span, Spanned};
use swc_ecma_ast::{
ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, FnDecl, Ident, Lit,
MemberExpr, MemberProp, ModuleDecl, ModuleItem, Number, ObjectLit, ObjectPat, Param, Pat, Str,
TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType,
TsParenthesizedType, TsPropertySignature, TsType, TsTypeAnn, TsTypeElement, TsTypeLit,
TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, FnDecl, Ident,
IdentName, Lit, MemberExpr, MemberProp, ModuleDecl, ModuleItem, Number, ObjectLit, ObjectPat,
Param, Pat, Str, TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType,
TsOptionalType, TsParenthesizedType, TsPropertySignature, TsType, TsTypeAnn, TsTypeElement,
TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
};
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_parser::{lexer::Lexer, EsSyntax, Parser, StringInput, Syntax, TsSyntax};
use regex::Regex;
#[cfg(target_arch = "wasm32")]
@@ -48,9 +48,9 @@ impl Visit for ImportsFinder {
pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()), code.into());
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into());
let lexer = Lexer::new(
Syntax::Typescript(TsConfig::default()),
Syntax::Typescript(TsSyntax::default()),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),
@@ -69,7 +69,7 @@ pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
})?;
let mut visitor = ImportsFinder { imports: HashSet::new() };
swc_ecma_visit::visit_module(&mut visitor, &expr);
visitor.visit_module(&expr);
Ok(visitor.imports.into_iter().collect())
}
@@ -87,7 +87,7 @@ impl Visit for OutputFinder {
c.visit_with(self);
}
match m {
MemberExpr { obj, prop: MemberProp::Ident(Ident { sym, .. }), .. } => {
MemberExpr { obj, prop: MemberProp::Ident(IdentName { sym, .. }), .. } => {
match *obj.to_owned() {
Expr::Ident(Ident { sym: sym_i, .. }) => {
self.idents.insert((sym_i.to_string(), sym.to_string()));
@@ -102,10 +102,10 @@ impl Visit for OutputFinder {
pub fn parse_expr_for_ids(code: &str) -> anyhow::Result<Vec<(String, String)>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.ts".into()), code.into());
let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Es(EsConfig { jsx: false, ..Default::default() }),
Syntax::Es(EsSyntax { jsx: false, ..Default::default() }),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),
@@ -124,7 +124,7 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result<Vec<(String, String)>> {
})?;
let mut visitor = OutputFinder { idents: HashSet::new() };
swc_ecma_visit::visit_module(&mut visitor, &expr);
visitor.visit_module(&expr);
Ok(visitor.idents.into_iter().collect())
}
@@ -135,10 +135,10 @@ pub fn parse_deno_signature(
main_override: Option<String>,
) -> anyhow::Result<MainArgSignature> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.ts".into()), code.into());
let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Typescript(TsConfig::default()),
Syntax::Typescript(TsSyntax::default()),
// EsVersion defaults to es5
Default::default(),
StringInput::from(&*fm),

View File

@@ -116,7 +116,7 @@ where
pub fn main() -> anyhow::Result<()> {
#[cfg(feature = "deno_core")]
deno_core::JsRuntime::init_platform(None);
deno_core::JsRuntime::init_platform(None, false);
create_and_run_current_thread_inner(windmill_main())
}
@@ -476,7 +476,7 @@ Windmill Community Edition {GIT_VERSION}
#[cfg(feature = "tantivy")]
let (index_reader, index_writer) = if should_index_jobs {
let (r, w) = windmill_indexer::indexer_ee::init_index().await?;
let (r, w) = windmill_indexer::indexer_ee::init_index(&db).await?;
(Some(r), Some(w))
} else {
(None, None)

View File

@@ -27,7 +27,7 @@ use windmill_api::{
DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT, SAML_METADATA, SCIM_TOKEN,
};
#[cfg(feature = "enterprise")]
use windmill_common::ee::worker_groups_alerts;
use windmill_common::ee::{worker_groups_alerts, jobs_waiting_alerts};
use windmill_common::{
auth::JWT_SECRET,
ee::CriticalErrorChannel,
@@ -1061,12 +1061,20 @@ pub async fn monitor_db(
}
};
let jobs_waiting_alerts_f = async {
#[cfg(feature = "enterprise")]
if server_mode {
jobs_waiting_alerts(&db).await;
}
};
join!(
expired_items_f,
zombie_jobs_f,
expose_queue_metrics_f,
verify_license_key_f,
worker_groups_alerts_f
worker_groups_alerts_f,
jobs_waiting_alerts_f,
);
}

View File

@@ -2745,7 +2745,7 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
"lock": null,
"path": null,
"type": "rawscript",
"content": "import * as wmill from \"https://deno.land/x/windmill@v1.50.0/mod.ts\"\n\nexport async function main() {\n return \"Hello\"\n}\n",
"content": "import * as wmill from \"https://deno.land/x/windmill@v1.50.0/mod.ts\"\n\nexport async function main() {\n return wmill\n}\n",
"language": "deno",
"input_transforms": {}
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.405.5
version: 1.409.2
title: Windmill API
contact:
@@ -2789,7 +2789,14 @@ paths:
oauth:
type: array
items:
type: string
type: object
properties:
type:
type: string
display_name:
type: string
required:
- type
saml:
type: string
required:
@@ -4013,6 +4020,42 @@ paths:
schema:
$ref: "#/components/schemas/Script"
/w/{workspace}/scripts/get_triggers_count/{path}:
get:
summary: get triggers count of script
operationId: getTriggersCountOfScript
tags:
- script
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: triggers count
content:
application/json:
schema:
$ref: "#/components/schemas/TriggersCount"
/w/{workspace}/scripts/list_tokens/{path}:
get:
summary: get tokens with script scope
operationId: listTokensOfScript
tags:
- script
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: tokens list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TruncatedToken"
/w/{workspace}/scripts/get/draft/{path}:
get:
summary: get script by path with draft
@@ -4617,6 +4660,43 @@ paths:
schema:
$ref: "#/components/schemas/Flow"
/w/{workspace}/flows/get_triggers_count/{path}:
get:
summary: get triggers count of flow
operationId: getTriggersCountOfFlow
tags:
- flow
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: triggers count
content:
application/json:
schema:
$ref: "#/components/schemas/TriggersCount"
/w/{workspace}/flows/list_tokens/{path}:
get:
summary: get tokens with flow scope
operationId: listTokensOfFlow
tags:
- flow
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: tokens list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TruncatedToken"
/w/{workspace}/flows/toggle_workspace_error_handler/{path}:
post:
summary: Toggle ON and OFF the workspace error handler for a given flow
@@ -10247,6 +10327,8 @@ components:
type: array
items:
type: string
email:
type: string
required:
- token_prefix
- created_at
@@ -10264,6 +10346,8 @@ components:
type: array
items:
type: string
workspace_id:
type: string
NewTokenImpersonate:
type: object
@@ -10275,6 +10359,8 @@ components:
format: date-time
impersonate_email:
type: string
workspace_id:
type: string
required:
- impersonate_email
@@ -11111,6 +11197,23 @@ components:
- requires_auth
- http_method
TriggersCount:
type: object
properties:
primary_schedule:
type: object
properties:
schedule:
type: string
schedule_count:
type: number
http_routes_count:
type: number
webhook_count:
type: number
email_count:
type: number
Group:
type: object
properties:

View File

@@ -199,11 +199,6 @@ pub async fn migrate(db: &DB) -> Result<(), Error> {
Err(err) => Err(err),
}?;
#[cfg(feature = "enterprise")]
if let Err(e) = windmill_migrations(&mut custom_migrator, db).await {
tracing::error!("Could not apply windmill custom migrations: {e:#}")
}
Ok(())
}
@@ -497,33 +492,6 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
Ok(())
}
#[cfg(feature = "enterprise")]
async fn windmill_migrations(migrator: &mut CustomMigrator, db: &DB) -> Result<(), Error> {
if std::env::var("MIGRATION_NO_BYPASSRLS").is_ok() {
migrator.lock().await?;
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'bypassrls_1-2')",
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
let query = include_str!("../../custom_migrations/bypassrls_1.sql");
tracing::info!("Applying bypassrls_1.sql");
let mut tx: sqlx::Transaction<'_, Postgres> = db.begin().await?;
tx.execute(query).await?;
tracing::info!("Applied bypassrls_1.sql");
sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('bypassrls_1-2')")
.execute(&mut *tx)
.await?;
tx.commit().await?;
}
migrator.unlock().await?;
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct ApiAuthed {
pub email: String,

View File

@@ -9,6 +9,9 @@
use std::collections::HashMap;
use crate::db::ApiAuthed;
use crate::triggers::{
get_triggers_count_internal, list_tokens_internal, TriggersCount, TruncatedTokenWithEmail,
};
use crate::utils::WithStarredInfoQuery;
use crate::{
db::DB,
@@ -53,6 +56,8 @@ pub fn workspaced_service() -> Router {
.route("/update/*path", post(update_flow))
.route("/archive/*path", post(archive_flow_by_path))
.route("/delete/*path", delete(delete_flow_by_path))
.route("/get_triggers_count/*path", get(get_triggers_count))
.route("/list_tokens/*path", get(list_tokens))
.route("/get/*path", get(get_flow_by_path))
.route("/get/draft/*path", get(get_flow_by_path_w_draft))
.route("/exists/*path", get(exists_flow_by_path))
@@ -874,6 +879,22 @@ async fn update_flow(
Ok(nf.path.to_string())
}
async fn get_triggers_count(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<TriggersCount> {
let path = path.to_path();
get_triggers_count_internal(&db, &w_id, &path, true).await
}
async fn list_tokens(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<Vec<TruncatedTokenWithEmail>> {
let path = path.to_path();
list_tokens_internal(&db, &w_id, &path, true).await
}
async fn get_flow_by_path(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -275,8 +275,10 @@ pub fn require_is_owner(authed: &ApiAuthed, name: &str) -> Result<()> {
async fn update_folder(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path((w_id, name)): Path<(String, String)>,
Json(mut ng): Json<UpdateFolder>,
) -> Result<String> {
@@ -367,6 +369,18 @@ async fn update_folder(
}
}
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Folder { path: format!("f/{}", name) },
Some(format!("Folder '{}' updated", name)),
rsmq,
true,
)
.await?;
audit_log(
&mut *tx,
&authed,

View File

@@ -24,7 +24,7 @@ use windmill_common::jobs::{
format_completed_job_result, format_result, CompletedJobWithFormattedResult, FormattedResult,
ENTRYPOINT_OVERRIDE,
};
use windmill_common::worker::TMP_DIR;
use windmill_common::worker::{CLOUD_HOSTED, TMP_DIR};
#[cfg(all(feature = "enterprise", feature = "parquet"))]
use windmill_common::scripts::PREVIEW_IS_CODEBASE_HASH;
@@ -81,7 +81,7 @@ use windmill_common::{METRICS_DEBUG_ENABLED, METRICS_ENABLED};
use windmill_common::{get_latest_deployed_hash_for_path, BASE_URL};
use windmill_queue::{
cancel_job, get_queued_job, get_result_by_id_from_running_flow, job_is_complete, push,
DecodeQueries, PushArgs, PushArgsOwned, PushIsolationLevel, QueueTransaction,
DecodeQueries, PushArgs, PushArgsOwned, PushIsolationLevel,
};
#[cfg(feature = "prometheus")]
@@ -544,8 +544,8 @@ pub async fn get_path_for_hash<'c>(
Ok(path)
}
pub async fn get_path_tag_limits_cache_for_hash<'c, R: rsmq_async::RsmqConnection + Send>(
tx: &mut QueueTransaction<'c, R>,
pub async fn get_path_tag_limits_cache_for_hash(
tx: &DB,
w_id: &str,
hash: i64,
) -> error::Result<(
@@ -1469,6 +1469,8 @@ async fn cancel_jobs(
}
}
uuids.extend(trivial_jobs);
Ok(Json(uuids))
}
@@ -2810,7 +2812,6 @@ pub async fn run_flow_by_path_inner(
let flow_path = flow_path.to_path();
check_scopes(&authed, || format!("run:flow/{flow_path}"))?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (tag, dedicated_worker, has_preprocessor) = sqlx::query!(
"SELECT tag, dedicated_worker, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor
@@ -2821,7 +2822,7 @@ pub async fn run_flow_by_path_inner(
flow_path,
w_id
)
.fetch_optional(&mut tx)
.fetch_optional(&db)
.await?
.map(|x| (x.tag, x.dedicated_worker, x.has_preprocessor))
.ok_or_else(|| {
@@ -2834,7 +2835,7 @@ pub async fn run_flow_by_path_inner(
check_tag_available_for_workspace(&w_id, &tag).await?;
let scheduled_for = run_query.get_scheduled_for(&db).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
tx,
@@ -2906,14 +2907,13 @@ pub async fn restart_flow(
) -> error::Result<(StatusCode, String)> {
check_license_key_valid().await?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let completed_job = sqlx::query_as::<_, CompletedJob>(
"SELECT *, result->'wm_labels' as labels from completed_job WHERE id = $1 and workspace_id = $2",
)
.bind(job_id)
.bind(&w_id)
.fetch_optional(&mut tx)
.fetch_optional(&db)
.await?
.with_context(|| "Unable to find completed job with the given job UUID")?;
@@ -2931,7 +2931,7 @@ pub async fn restart_flow(
let scheduled_for = run_query.get_scheduled_for(&db).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -3007,16 +3007,16 @@ pub async fn run_script_by_path_inner(
check_scopes(&authed, || format!("run:script/{script_path}"))?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, _delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
script_path_to_payload(script_path, &db, &w_id, run_query.skip_preprocessor).await?;
let scheduled_for = run_query.get_scheduled_for(&db).await?;
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -3049,6 +3049,11 @@ pub async fn run_script_by_path_inner(
Ok((StatusCode::CREATED, uuid.to_string()))
}
#[derive(Deserialize)]
pub struct WorkflowAsCodeQuery {
pub skip_update: Option<bool>,
}
pub async fn run_workflow_as_code(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -3056,15 +3061,38 @@ pub async fn run_workflow_as_code(
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
Path((w_id, job_id, entrypoint)): Path<(String, Uuid, String)>,
Query(run_query): Query<RunJobQuery>,
Query(wkflow_query): Query<WorkflowAsCodeQuery>,
Json(task): Json<WorkflowTask>,
) -> error::Result<(StatusCode, String)> {
let mut i = 1;
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
check_tag_available_for_workspace(&w_id, &run_query.tag).await?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
let job = get_queued_job(&job_id, &w_id, &db).await?;
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
let job = not_found_if_none(job, "Queued Job", &job_id.to_string())?;
let (job_payload, tag, _delete_after_use, timeout) = match job.job_kind {
JobKind::Preview => (
@@ -3089,7 +3117,7 @@ pub async fn run_workflow_as_code(
JobKind::Script => {
script_path_to_payload(
job.script_path(),
&mut tx,
&db,
&w_id,
run_query.skip_preprocessor,
)
@@ -3098,6 +3126,12 @@ pub async fn run_workflow_as_code(
_ => return Err(anyhow::anyhow!("Not supported").into()),
};
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
let mut extra = HashMap::new();
extra.insert(ENTRYPOINT_OVERRIDE.to_string(), to_raw_value(&entrypoint));
@@ -3106,7 +3140,21 @@ pub async fn run_workflow_as_code(
let tag = run_query.tag.clone().or(tag).or(Some(job.tag));
let tx = PushIsolationLevel::Transaction(tx);
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
let (uuid, mut tx) = push(
&db,
@@ -3133,14 +3181,39 @@ pub async fn run_workflow_as_code(
Some(&authed.clone().into()),
)
.await?;
sqlx::query!(
"UPDATE queue SET flow_status = jsonb_set(COALESCE(flow_status, '{}'::jsonb), array[$1], jsonb_set(jsonb_set('{}'::jsonb, '{scheduled_for}', to_jsonb(now()::text)), '{name}', to_jsonb($4::text))) WHERE id = $2 AND workspace_id = $3",
uuid.to_string(),
job_id,
w_id,
entrypoint
).execute(&mut tx).await?;
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
if !wkflow_query.skip_update.unwrap_or(false) {
sqlx::query!(
"UPDATE queue SET flow_status = jsonb_set(COALESCE(flow_status, '{}'::jsonb), array[$1], jsonb_set(jsonb_set('{}'::jsonb, '{scheduled_for}', to_jsonb(now()::text)), '{name}', to_jsonb($4::text))) WHERE id = $2 AND workspace_id = $3",
uuid.to_string(),
job_id,
w_id,
entrypoint
).execute(&mut tx).await?;
} else {
tracing::info!("Skipping update of flow status for job {job_id} in workspace {w_id}");
}
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
i += 1;
}
tx.commit().await?;
if *CLOUD_HOSTED {
tracing::info!("workflow_as_code_tracing id {i} ");
}
Ok((StatusCode::CREATED, uuid.to_string()))
}
@@ -3504,15 +3577,13 @@ pub async fn run_wait_result_job_by_path_get(
let script_path = script_path.to_path();
check_scopes(&authed, || format!("run:script/{script_path}"))?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
script_path_to_payload(script_path, &db, &w_id, run_query.skip_preprocessor).await?;
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -3629,15 +3700,13 @@ pub async fn run_wait_result_script_by_path_internal(
let script_path = script_path.to_path();
check_scopes(&authed, || format!("run:script/{script_path}"))?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let (job_payload, tag, delete_after_use, timeout) =
script_path_to_payload(script_path, &mut tx, &w_id, run_query.skip_preprocessor).await?;
script_path_to_payload(script_path, &db, &w_id, run_query.skip_preprocessor).await?;
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -3689,8 +3758,6 @@ pub async fn run_wait_result_script_by_hash(
check_queue_too_long(&db, run_query.queue_limit).await?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let hash = script_hash.0;
let (
path,
@@ -3705,7 +3772,7 @@ pub async fn run_wait_result_script_by_hash(
delete_after_use,
timeout,
has_preprocessor,
) = get_path_tag_limits_cache_for_hash(&mut tx, &w_id, hash).await?;
) = get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
if let Some(run_query_cache_ttl) = run_query.cache_ttl {
cache_ttl = Some(run_query_cache_ttl);
}
@@ -3714,7 +3781,7 @@ pub async fn run_wait_result_script_by_hash(
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -3796,7 +3863,6 @@ pub async fn run_wait_result_flow_by_path_internal(
let flow_path = flow_path.to_path();
check_scopes(&authed, || format!("run:flow/{flow_path}"))?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let scheduled_for = run_query.get_scheduled_for(&db).await?;
@@ -3809,7 +3875,7 @@ pub async fn run_wait_result_flow_by_path_internal(
flow_path,
w_id
)
.fetch_optional(&mut tx)
.fetch_optional(&db)
.await?
.map(|x| (x.tag, x.dedicated_worker, x.early_return, x.has_preprocessor))
.ok_or_else(|| {
@@ -3821,7 +3887,7 @@ pub async fn run_wait_result_flow_by_path_internal(
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,
@@ -4293,7 +4359,6 @@ async fn add_batch_jobs(
}
}
"flow" => {
let mut tx = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq);
let mut uuids: Vec<Uuid> = Vec::new();
let payload = if let Some(ref fv) = batch_info.flow_value {
@@ -4311,6 +4376,7 @@ async fn add_batch_jobs(
))?
}
};
let mut tx = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq);
for _ in 0..n {
let ehm = HashMap::new();
let (uuid, ntx) = push(
@@ -4511,7 +4577,6 @@ pub async fn run_job_by_hash_inner(
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let hash = script_hash.0;
let (
@@ -4527,7 +4592,7 @@ pub async fn run_job_by_hash_inner(
_delete_after_use, // not taken into account in async endpoints
timeout,
has_preprocessor,
) = get_path_tag_limits_cache_for_hash(&mut tx, &w_id, hash).await?;
) = get_path_tag_limits_cache_for_hash(&db, &w_id, hash).await?;
check_scopes(&authed, || format!("run:script/{path}"))?;
if let Some(run_query_cache_ttl) = run_query.cache_ttl {
cache_ttl = Some(run_query_cache_ttl);
@@ -4536,7 +4601,7 @@ pub async fn run_job_by_hash_inner(
let tag = run_query.tag.clone().or(tag);
check_tag_available_for_workspace(&w_id, &tag).await?;
let tx = PushIsolationLevel::Transaction(tx);
let tx = PushIsolationLevel::Isolated(user_db, authed.clone().into(), rsmq);
let (uuid, tx) = push(
&db,

View File

@@ -82,6 +82,7 @@ pub mod smtp_server_ee;
mod static_assets;
mod stripe_ee;
mod tracing_init;
mod triggers;
mod users;
mod utils;
mod variables;

View File

@@ -9,6 +9,9 @@
use crate::{
db::{ApiAuthed, DB},
schedule::clear_schedule,
triggers::{
get_triggers_count_internal, list_tokens_internal, TriggersCount, TruncatedTokenWithEmail,
},
users::{maybe_refresh_folders, require_owner_of_path, AuthCache},
utils::WithStarredInfoQuery,
webhook_util::{WebhookMessage, WebhookShared},
@@ -132,6 +135,8 @@ pub fn workspaced_service() -> Router {
.route("/archive/p/*path", post(archive_script_by_path))
.route("/get/draft/*path", get(get_script_by_path_w_draft))
.route("/get/p/*path", get(get_script_by_path))
.route("/get_triggers_count/*path", get(get_triggers_count))
.route("/list_tokens/*path", get(list_tokens))
.route("/raw/p/*path", get(raw_script_by_path))
.route("/raw_unpinned/p/*path", get(raw_script_by_path_unpinned))
.route("/exists/p/*path", get(exists_script_by_path))
@@ -874,6 +879,22 @@ async fn get_script_by_path(
Ok(Json(script))
}
async fn list_tokens(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<Vec<TruncatedTokenWithEmail>> {
let path = path.to_path();
list_tokens_internal(&db, &w_id, &path, false).await
}
async fn get_triggers_count(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<TriggersCount> {
let path = path.to_path();
get_triggers_count_internal(&db, &w_id, &path, false).await
}
async fn get_script_by_path_w_draft(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,

View File

@@ -9,6 +9,7 @@
use ::tracing::{field, Span};
use hyper::Response;
use tower_http::trace::{MakeSpan, OnFailure, OnResponse};
use uuid::Uuid;
lazy_static::lazy_static! {
static ref LOG_REQUESTS: bool = std::env::var("LOG_REQUESTS")
@@ -45,17 +46,28 @@ impl<B> OnFailure<B> for MyOnFailure {
// tracing::error!(latency = latency.as_millis(), "response")
}
}
lazy_static::lazy_static! {
static ref TRACING_HEADER: String = std::env::var("TRACING_HEADER")
.ok().unwrap_or_else(|| "x-tracing-id".to_string());
}
#[derive(Clone)]
pub struct MyMakeSpan {}
impl<B> MakeSpan<B> for MyMakeSpan {
fn make_span(&mut self, request: &hyper::Request<B>) -> Span {
let tracing_id = request
.headers()
.get(TRACING_HEADER.as_str())
.and_then(|x| x.to_str().map(|x| x.to_string()).ok())
.unwrap_or(Uuid::new_v4().to_string());
tracing::info_span!(
"request",
method = %request.method(),
uri = %request.uri(),
username = field::Empty,
workspace_id = field::Empty,
trace_id = tracing_id,
email = field::Empty,
)
}

View File

@@ -0,0 +1,131 @@
use axum::Json;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use windmill_common::error::JsonResult;
use crate::db::DB;
#[derive(Serialize, Deserialize, Debug)]
pub struct TriggerPrimarySchedule {
schedule: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TriggersCount {
primary_schedule: Option<TriggerPrimarySchedule>,
schedule_count: i64,
http_routes_count: i64,
webhook_count: i64,
email_count: i64,
}
pub(crate) async fn get_triggers_count_internal(
db: &DB,
w_id: &str,
path: &str,
is_flow: bool,
) -> JsonResult<TriggersCount> {
let primary_schedule = sqlx::query_scalar!(
"SELECT schedule FROM schedule WHERE path = $1 AND script_path = $1 AND is_flow = $2 AND workspace_id = $3",
path,
is_flow,
w_id
)
.fetch_optional(db)
.await?;
let schedule_count = sqlx::query_scalar!(
"SELECT COUNT(*) FROM schedule 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 http_routes_count = sqlx::query_scalar!(
"SELECT COUNT(*) FROM http_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[]",
w_id,
path,
)
} else {
sqlx::query_scalar!(
"SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:' || $2]::text[]",
w_id,
path,
)
}).fetch_one(db)
.await?
.unwrap_or(0);
let email_count = (if is_flow {
sqlx::query_scalar!(
"SELECT COUNT(*) FROM token WHERE label LIKE 'email-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
w_id,
path,
)
} else {
sqlx::query_scalar!(
"SELECT COUNT(*) FROM token WHERE label LIKE 'email-%' AND workspace_id = $1 AND scopes @> ARRAY['run:script/' || $2]::text[]",
w_id,
path,
)
}).fetch_one(db)
.await?
.unwrap_or(0);
Ok(Json(TriggersCount {
primary_schedule: primary_schedule.map(|s| TriggerPrimarySchedule { schedule: s }),
schedule_count,
http_routes_count,
webhook_count,
email_count,
}))
}
#[derive(FromRow, Serialize)]
pub struct TruncatedTokenWithEmail {
pub label: Option<String>,
pub token_prefix: Option<String>,
pub expiration: Option<chrono::DateTime<chrono::Utc>>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub last_used_at: chrono::DateTime<chrono::Utc>,
pub scopes: Option<Vec<String>>,
pub email: Option<String>,
}
pub async fn list_tokens_internal(
db: &DB,
w_id: &str,
path: &str,
is_flow: bool,
) -> JsonResult<Vec<TruncatedTokenWithEmail>> {
let tokens = if is_flow {
sqlx::query_as!(
TruncatedTokenWithEmail,
"SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes, email FROM token WHERE workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
w_id, path).fetch_all(db)
.await?
} else {
sqlx::query_as!(
TruncatedTokenWithEmail,
"SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes, email FROM token WHERE workspace_id = $1 AND scopes @> ARRAY['run:script/' || $2]::text[]",
w_id, path)
.fetch_all(db)
.await?
};
Ok(Json(tokens))
}

View File

@@ -128,7 +128,13 @@ pub fn make_unauthed_service() -> Router {
fn username_override_from_label(label: Option<String>) -> Option<String> {
match label {
Some(label) if label.starts_with("webhook-") => Some(label),
Some(label)
if label.starts_with("webhook-")
|| label.starts_with("http-")
|| label.starts_with("email-") =>
{
Some(label)
}
Some(label) if label.starts_with("ephemeral-script-end-user-") => Some(
label
.trim_start_matches("ephemeral-script-end-user-")
@@ -270,9 +276,10 @@ impl AuthCache {
_ => {
let user_o = sqlx::query_as::<_, (Option<String>, Option<String>, bool, Option<Vec<String>>, Option<String>)>(
"UPDATE token SET last_used_at = now() WHERE token = $1 AND (expiration > NOW() \
OR expiration IS NULL) RETURNING owner, email, super_admin, scopes, label",
OR expiration IS NULL) AND (workspace_id IS NULL OR workspace_id = $2) RETURNING owner, email, super_admin, scopes, label",
)
.bind(token)
.bind(w_id.as_ref())
.fetch_optional(&self.db)
.await
.ok()
@@ -837,6 +844,7 @@ pub struct NewToken {
pub expiration: Option<chrono::DateTime<chrono::Utc>>,
pub impersonate_email: Option<String>,
pub scopes: Option<Vec<String>>,
pub workspace_id: Option<String>,
}
#[derive(Deserialize)]
@@ -2389,14 +2397,15 @@ async fn create_token(
.unwrap_or(false);
sqlx::query!(
"INSERT INTO token
(token, email, label, expiration, super_admin, scopes)
VALUES ($1, $2, $3, $4, $5, $6)",
(token, email, label, expiration, super_admin, scopes, workspace_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)",
token,
authed.email,
new_token.label,
new_token.expiration,
is_super_admin,
new_token.scopes.as_ref().map(|x| x.as_slice())
new_token.scopes.as_ref().map(|x| x.as_slice()),
new_token.workspace_id,
)
.execute(&mut *tx)
.await?;

View File

@@ -81,3 +81,6 @@ pub async fn create_customer_portal_session(
#[cfg(feature = "enterprise")]
pub async fn worker_groups_alerts(_db: &DB) {}
#[cfg(feature = "enterprise")]
pub async fn jobs_waiting_alerts(_db: &DB) {}

View File

@@ -29,3 +29,4 @@ tempfile.workspace = true
bytes.workspace = true
object_store = { workspace = true, optional = true}
tokio-tar.workspace = true
lazy_static.workspace = true

View File

@@ -5,15 +5,17 @@ use deno_net::NetPermissions;
#[cfg(feature = "deno_core")]
use deno_web::{BlobStore, TimersPermission};
#[cfg(feature = "deno_core")]
use std::borrow::Cow;
#[cfg(feature = "deno_core")]
use std::env;
#[cfg(feature = "deno_core")]
use std::io::Write;
#[cfg(feature = "deno_core")]
use std::path::PathBuf;
use std::path::{Path, PathBuf};
#[cfg(feature = "deno_core")]
use std::sync::Arc;
#[cfg(feature = "deno_core")]
// #[cfg(feature = "deno_core")]
pub struct PermissionsContainer;
#[cfg(feature = "deno_core")]
@@ -24,16 +26,16 @@ impl FetchPermissions for PermissionsContainer {
_url: &deno_core::url::Url,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
unreachable!("snapshotting")
}
#[inline(always)]
fn check_read(
fn check_read<'a>(
&mut self,
_p: &std::path::Path,
_p: &'a std::path::Path,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<Cow<'a, Path>, deno_core::error::AnyError> {
unreachable!("snapshotting")
}
}
@@ -47,20 +49,20 @@ impl TimersPermission for PermissionsContainer {
#[cfg(feature = "deno_core")]
impl NetPermissions for PermissionsContainer {
fn check_read(
fn check_read<'a>(
&mut self,
_p: &std::path::Path,
_p: &'a str,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<PathBuf, deno_core::error::AnyError> {
unreachable!("snapshotting")
}
fn check_write(
fn check_write<'a>(
&mut self,
_p: &std::path::Path,
_p: &'a str,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<PathBuf, deno_core::error::AnyError> {
unreachable!("snapshotting")
}
fn check_net<T: AsRef<str>>(
@@ -68,7 +70,15 @@ impl NetPermissions for PermissionsContainer {
_host: &(T, Option<u16>),
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
unreachable!("snapshotting")
}
fn check_write_path<'a>(
&mut self,
_: &'a Path,
_: &str,
) -> Result<Cow<'a, Path>, deno_core::anyhow::Error> {
todo!()
}
}

View File

@@ -97,6 +97,13 @@ mount {
is_bind: true
}
mount {
dst: "/dev/shm"
fstype: "tmpfs"
rw: true
is_bind: false
}
mount {
src: "/dev/random"
dst: "/dev/random"

View File

@@ -318,15 +318,33 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
POWERSHELL_CACHE_DIR
);
// NOTE: powershell error handling / termination is quite tricky compared to bash
// here we're trying to catch terminating errors and propagate the exit code
// to the caller such that the job will be marked as failed. It's up to the user
// to catch specific errors in their script not caught by the below as there is no
// generic set -eu as in bash
let strict_termination_start = "$ErrorActionPreference = 'Stop'\n\
Set-StrictMode -Version Latest\n\
try {\n";
let strict_termination_end = "\n\
} catch {\n\
Write-Output \"An error occurred:\n\"\
Write-Output $_
exit 1\n\
}\n";
// make sure param() is first
let param_match = windmill_parser_bash::RE_POWERSHELL_PARAM.find(&content);
let content: String = if let Some(param_match) = param_match {
let param_match = param_match.as_str();
format!(
"{}\n{}\n{}",
"{}\n{}\n{}\n{}\n{}",
param_match,
profile,
content.replace(param_match, "")
strict_termination_start,
content.replace(param_match, ""),
strict_termination_end
)
} else {
format!("{}\n{}", profile, content)
@@ -351,7 +369,8 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
$pipe = New-TemporaryFile\n\
& \"{}\" -File ./main.ps1 @args 2>&1 | Tee-Object -FilePath $pipe\n\
Get-Content -Path $pipe | Select-Object -Last 1 | Set-Content -Path './result2.out'\n\
Remove-Item $pipe\n",
Remove-Item $pipe\n\
exit $LASTEXITCODE\n",
POWERSHELL_PATH.as_str()
),
)?;

View File

@@ -1105,6 +1105,16 @@ try {{
if (step_id) {{
err["step_id"] = step_id;
}}
const extra = {{}};
Object.getOwnPropertyNames(e).forEach((key) => {{
if (['line', 'name', 'stack', 'column', 'message', 'sourceURL', 'originalLine', 'originalColumn'].includes(key)) {{
return;
}}
extra[key] = e[key];
}});
if (Object.keys(extra).length > 0) {{
err["extra"] = extra;
}}
await fs.writeFile("result.json", JSON.stringify(err));
process.exit(1);
}}

View File

@@ -125,7 +125,8 @@ pub async fn generate_deno_lock(
"--unstable-worker-options",
"--unstable-http",
"--lock=lock.json",
"--lock-write",
"--frozen=false",
"--allow-import",
"--import-map",
&import_map_path,
"main.ts",
@@ -156,10 +157,13 @@ pub async fn generate_deno_lock(
}
let path_lock = format!("{job_dir}/lock.json");
let mut file = File::open(path_lock).await?;
let mut req_content = "".to_string();
file.read_to_string(&mut req_content).await?;
Ok(req_content)
if let Ok(mut file) = File::open(path_lock).await {
let mut req_content = "".to_string();
file.read_to_string(&mut req_content).await?;
Ok(req_content)
} else {
Ok("".to_string())
}
}
#[tracing::instrument(level = "trace", skip_all)]
@@ -366,10 +370,10 @@ try {{
} else if !*DISABLE_NSJAIL {
args.push("--allow-net");
args.push("--allow-sys");
args.push("--allow-hrtime");
args.push(allow_read.as_str());
args.push("--allow-write=./");
args.push("--allow-env");
args.push("--allow-import");
args.push("--allow-run=git,/usr/bin/chromium");
} else {
args.push("-A");

View File

@@ -8,9 +8,11 @@
#[cfg(feature = "deno_core")]
use std::{
borrow::Cow,
cell::RefCell,
env,
io::{self, BufReader},
path::PathBuf,
rc::Rc,
};
@@ -111,12 +113,12 @@ impl FetchPermissions for PermissionsContainer {
}
#[inline(always)]
fn check_read(
fn check_read<'a>(
&mut self,
_p: &std::path::Path,
p: &'a std::path::Path,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<Cow<'a, std::path::Path>, anyhow::Error> {
Ok(Cow::Borrowed(p))
}
}
@@ -130,20 +132,20 @@ impl TimersPermission for PermissionsContainer {
#[cfg(feature = "deno_core")]
impl NetPermissions for PermissionsContainer {
fn check_read(
fn check_read<'a>(
&mut self,
_p: &std::path::Path,
p: &'a str,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<PathBuf, deno_core::error::AnyError> {
Ok(PathBuf::from(p))
}
fn check_write(
fn check_write<'a>(
&mut self,
_p: &std::path::Path,
p: &'a str,
_api_name: &str,
) -> Result<(), deno_core::error::AnyError> {
Ok(())
) -> Result<PathBuf, deno_core::error::AnyError> {
Ok(PathBuf::from(p))
}
fn check_net<T: AsRef<str>>(
@@ -153,6 +155,14 @@ impl NetPermissions for PermissionsContainer {
) -> Result<(), deno_core::error::AnyError> {
Ok(())
}
fn check_write_path<'a>(
&mut self,
p: &'a std::path::Path,
_api_name: &str,
) -> Result<std::borrow::Cow<'a, std::path::Path>, AnyError> {
Ok(Cow::Borrowed(p))
}
}
#[cfg(feature = "deno_core")]
@@ -237,7 +247,7 @@ pub async fn eval_timeout(
#[cfg(not(feature = "deno_core"))]
{
#[allow(unreachable_code)]
return todo!();
return Err(anyhow::anyhow!("Deno core is not enabled".to_string()).into());
}
#[cfg(feature = "deno_core")]

View File

@@ -498,7 +498,10 @@ except BaseException as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
tb = traceback.format_tb(exc_traceback)
with open(result_json, 'w') as f:
err = {{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }}
err = {{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }}
extra = e.__dict__
if extra and len(extra) > 0:
err['extra'] = extra
flow_node_id = os.environ.get('WM_FLOW_STEP_ID')
if flow_node_id:
err['step_id'] = flow_node_id

View File

@@ -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.405.5";
export const VERSION = "v1.409.2";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -1,6 +1,6 @@
// deno-lint-ignore-file no-explicit-any
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
import { colors, Command, log, SEP, Table, yamlParse } from "./deps.ts";
import { colors, Command, log, SEP, Table, yamlParseFile } from "./deps.ts";
import * as wmill from "./gen/services.gen.ts";
import { ListableApp, Policy } from "./gen/types.gen.ts";
@@ -25,7 +25,7 @@ export async function pushApp(
return;
}
alreadySynced.push(localPath);
remotePath.replaceAll(SEP, "/");
remotePath = remotePath.replaceAll(SEP, "/");
let app: any = undefined;
// deleting old app if it exists in raw mode
try {
@@ -40,8 +40,8 @@ export async function pushApp(
if (!localPath.endsWith(SEP)) {
localPath += SEP;
}
const localAppRaw = await Deno.readTextFile(localPath + "app.yaml");
const localApp = yamlParse(localAppRaw) as AppFile;
const path = localPath + "app.yaml";
const localApp = (await yamlParseFile(path)) as AppFile;
function replaceInlineScripts(rec: any) {
if (!rec) {

View File

@@ -1,4 +1,4 @@
import { log, yamlParse } from "./deps.ts";
import { log, yamlParseFile } from "./deps.ts";
export interface SyncOptions {
stateful?: boolean;
@@ -40,9 +40,7 @@ export interface Codebase {
export async function readConfigFile(): Promise<SyncOptions> {
try {
const conf = yamlParse(
await Deno.readTextFile("wmill.yaml")
) as SyncOptions;
const conf = (await yamlParseFile("wmill.yaml")) as SyncOptions;
if (conf?.defaultTs == undefined) {
log.warn(
"No defaultTs defined in your wmill.yaml. Using 'bun' as default."

View File

@@ -21,7 +21,29 @@ export { copy } from "jsr:@std/io/copy";
export { readAll } from "jsr:@std/io/read-all";
export * as log from "jsr:@std/log";
export { stringify as yamlStringify, parse as yamlParse } from "jsr:@std/yaml";
export { stringify as yamlStringify } from "jsr:@std/yaml";
import { parse as yamlParse, ParseOptions } from "jsr:@std/yaml";
export async function yamlParseFile(path: string, options: ParseOptions = {}) {
try {
return yamlParse(await Deno.readTextFile(path), options);
} catch (e) {
throw new Error(`Error parsing yaml ${path}`, { cause: e });
}
}
export function yamlParseContent(
path: string,
content: string,
options: ParseOptions = {}
) {
try {
return yamlParse(content, options);
} catch (e) {
throw new Error(`Error parsing yaml ${path}`, { cause: e });
}
}
// other

View File

@@ -1,7 +1,7 @@
// deno-lint-ignore-file no-explicit-any
import { GlobalOptions, isSuperset } from "./types.ts";
import { Confirm, SEP, log, yamlStringify } from "./deps.ts";
import { colors, Command, Table, yamlParse } from "./deps.ts";
import { colors, Command, Table, yamlParseFile } from "./deps.ts";
import * as wmill from "./gen/services.gen.ts";
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
@@ -29,21 +29,23 @@ export function replaceInlineScripts(
) {
modules.forEach((m) => {
if (m.value.type == "rawscript") {
const path = m.value.content.split(" ")[1];
m.value.content = Deno.readTextFileSync(localPath + path);
const lock = m.value.lock;
if (removeLocks && removeLocks.includes(path)) {
m.value.lock = undefined;
} else if (
lock &&
typeof lock == "string" &&
lock.trimStart().startsWith("!inline ")
) {
const path = lock.split(" ")[1];
try {
m.value.lock = readInlinePathSync(localPath + path);
} catch {
log.error(`Lock file ${path} not found`);
if (m.value.content.startsWith("!inline")) {
const path = m.value.content.split(" ")[1];
m.value.content = Deno.readTextFileSync(localPath + path);
const lock = m.value.lock;
if (removeLocks && removeLocks.includes(path)) {
m.value.lock = undefined;
} else if (
lock &&
typeof lock == "string" &&
lock.trimStart().startsWith("!inline ")
) {
const path = lock.split(" ")[1];
try {
m.value.lock = readInlinePathSync(localPath + path);
} catch {
log.error(`Lock file ${path} not found`);
}
}
}
} else if (m.value.type == "forloopflow") {
@@ -87,8 +89,7 @@ export async function pushFlow(
if (!localPath.endsWith(SEP)) {
localPath += SEP;
}
const localFlowRaw = await Deno.readTextFile(localPath + "flow.yaml");
const localFlow = yamlParse(localFlowRaw) as FlowFile;
const localFlow = (await yamlParseFile(localPath + "flow.yaml")) as FlowFile;
replaceInlineScripts(localFlow.value.modules, localPath, undefined);
@@ -120,6 +121,7 @@ export async function pushFlow(
});
} catch (e) {
throw new Error(
//@ts-ignore
`Failed to create flow ${remotePath}: ${e.body ?? e.message}`
);
}

View File

@@ -73,6 +73,7 @@ export async function pushFolder(
},
});
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -87,6 +88,7 @@ export async function pushFolder(
},
});
} catch (e) {
//@ts-ignore
throw Error(`Failed to create folder ${name}: ${e.body ?? e.message}`);
}
}

View File

@@ -54,7 +54,7 @@ export const OpenAPI: OpenAPIConfig = {
PASSWORD: undefined,
TOKEN: getEnv("WM_TOKEN"),
USERNAME: undefined,
VERSION: '1.401.0',
VERSION: '1.407.2',
WITH_CREDENTIALS: true,
interceptors: {
request: new Interceptors(),

View File

@@ -1015,6 +1015,9 @@ export type FlowModule = {
skip_if_stopped?: boolean;
expr: string;
};
skip_if?: {
expr: string;
};
sleep?: InputTransform;
cache_ttl?: number;
timeout?: number;
@@ -1176,6 +1179,7 @@ export type FlowStatusModule = {
approver: string;
}>;
failed_retries?: Array<(string)>;
skipped?: boolean;
};
export type type4 = 'WaitingForPriorSteps' | 'WaitingForEvents' | 'WaitingForExecutor' | 'InProgress' | 'Success' | 'Failure';

View File

@@ -3,7 +3,7 @@ import {
path,
Confirm,
yamlStringify,
yamlParse,
yamlParseFile,
Command,
setClient,
Table,
@@ -25,8 +25,8 @@ import {
import {
add as workspaceSetup,
addWorkspace,
allWorkspaces,
removeWorkspace,
setActiveWorkspace,
} from "./workspace.ts";
import {
pushInstanceSettings,
@@ -35,8 +35,9 @@ import {
pushInstanceConfigs,
type SimplifiedSettings,
} from "./settings.ts";
import { sleep, deepEqual } from "./utils.ts";
import { deepEqual } from "./utils.ts";
import { GlobalOptions } from "./types.ts";
import { getActiveWorkspace } from "./workspace.ts";
export interface Instance {
remote: string;
@@ -293,22 +294,18 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
log.info("No instance-level changes to apply");
}
sleep(1000);
if (opts.includeWorkspaces) {
log.info("\nPulling all workspaces");
const rootDir = Deno.cwd();
const localWorkspaces = await getLocalWorkspaces(rootDir, instance.prefix);
const previousActiveWorkspace = await getActiveWorkspace(undefined);
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
page: 1,
perPage: 1000,
});
let localWorkspaces = await allWorkspaces();
localWorkspaces = localWorkspaces.filter((w) =>
w.name.startsWith(instance.prefix + "_")
);
const rootDir = Deno.cwd();
for (const remoteWorkspace of remoteWorkspaces) {
log.info("\nPulling workspace " + remoteWorkspace.id);
sleep(1000);
const workspaceName = instance.prefix + "_" + remoteWorkspace.id;
await Deno.mkdir(path.join(rootDir, workspaceName), {
recursive: true,
@@ -341,7 +338,7 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
}
const localWorkspacesToDelete = localWorkspaces.filter(
(w) => !remoteWorkspaces.find((r) => r.id === w.workspaceId)
(w) => !remoteWorkspaces.find((r) => r.id === w.id)
);
if (localWorkspacesToDelete.length > 0) {
@@ -350,20 +347,23 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
(await Confirm.prompt({
message:
"Do you want to delete the local copy of workspaces that don't exist anymore on the instance?\n" +
localWorkspacesToDelete.map((w) => w.workspaceId).join(", "),
localWorkspacesToDelete.map((w) => w).join(", "),
default: true,
}));
if (confirmDelete) {
for (const workspace of localWorkspacesToDelete) {
await removeWorkspace(workspace.name, false, {});
await Deno.remove(path.join(rootDir, workspace.name), {
await removeWorkspace(workspace.id, false, {});
await Deno.remove(path.join(rootDir, workspace.dir), {
recursive: true,
});
}
}
}
if (previousActiveWorkspace) {
await setActiveWorkspace(previousActiveWorkspace?.name);
}
log.info(colors.green.underline.bold("All workspaces pulled"));
}
}
@@ -421,10 +421,10 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
log.info("No instance-level changes to apply");
}
sleep(1000);
if (opts.includeWorkspaces) {
instances = await allInstances();
const rootDir = Deno.cwd();
const localPrefix = (await Select.prompt({
message: "What is the prefix of the local workspaces you want to sync?",
options: [
@@ -440,18 +440,18 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
page: 1,
perPage: 1000,
});
let localWorkspaces = await allWorkspaces();
localWorkspaces = localWorkspaces.filter((w) =>
w.name.startsWith(localPrefix + "_")
);
log.info("\nPushing all workspaces");
const rootDir = Deno.cwd();
const previousActiveWorkspace = await getActiveWorkspace(undefined);
const localWorkspaces = await getLocalWorkspaces(rootDir, localPrefix);
log.info(
`\nPushing all workspaces: ${localWorkspaces.map((x) => x.id).join(", ")}`
);
for (const localWorkspace of localWorkspaces) {
log.info("\nPushing workspace " + localWorkspace.workspaceId);
sleep(1000);
log.info("\nPushing workspace " + localWorkspace.id);
try {
await Deno.chdir(path.join(rootDir, localWorkspace.name));
await Deno.chdir(path.join(rootDir, localWorkspace.dir));
} catch (_) {
throw new Error(
"Workspace folder not found, are you in the right directory?"
@@ -459,9 +459,9 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
}
try {
const workspaceSettings = yamlParse(
await Deno.readTextFile("settings.yaml")
) as SimplifiedSettings;
const workspaceSettings = (await yamlParseFile(
"settings.yaml"
)) as SimplifiedSettings;
await workspaceSetup(
{
token: instance.token,
@@ -471,8 +471,8 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
createWorkspaceName: workspaceSettings.name,
createUsername: undefined,
},
localWorkspace.name,
localWorkspace.workspaceId,
localWorkspace.dir,
localWorkspace.id,
instance.remote
);
} catch (_) {
@@ -482,7 +482,7 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
continue;
}
await push({
workspace: localWorkspace.name,
workspace: localWorkspace.dir,
token: undefined,
baseUrl: undefined,
includeGroups: true,
@@ -495,7 +495,7 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
}
const workspacesToDelete = remoteWorkspaces.filter(
(w) => !localWorkspaces.find((l) => l.workspaceId === w.id)
(w) => !localWorkspaces.find((l) => l.id === w.id)
);
if (workspacesToDelete.length > 0) {
const confirmDelete =
@@ -514,10 +514,28 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
}
}
}
if (previousActiveWorkspace) {
await setActiveWorkspace(previousActiveWorkspace?.name);
}
log.info(colors.green.underline.bold("All workspaces pushed"));
}
}
async function getLocalWorkspaces(rootDir: string, localPrefix: string) {
const localWorkspaces: { dir: string; id: string }[] = [];
for await (const dir of Deno.readDir(rootDir)) {
const dirName = dir.name;
if (dirName.startsWith(localPrefix + "_")) {
localWorkspaces.push({
dir: dirName,
id: dirName.substring(localPrefix.length + 1),
});
}
}
return localWorkspaces;
}
async function switchI(opts: {}, instanceName: string) {
const all = await allInstances();
if (all.findIndex((x) => x.name === instanceName) === -1) {
@@ -560,6 +578,7 @@ async function whoami(opts: {}) {
log.info(JSON.stringify(whoamiInfo, null, 2));
} catch (error) {
log.error(
//@ts-ignore
colors.red(`Failed to retrieve whoami information: ${error.message}`)
);
}
@@ -602,7 +621,7 @@ const command = new Command()
.description("Remove an instance")
.complete("instance", async () => (await allInstances()).map((x) => x.name))
.arguments("<instance:string:instance>")
.action(async (instance) => {
.action(async (instance: any) => {
const instances = await allInstances();
const choice = (await Select.prompt({

View File

@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.405.5";
export const VERSION = "1.409.2";
const command = new Command()
.name("wmill")

View File

@@ -1,6 +1,13 @@
// deno-lint-ignore-file no-explicit-any
import { GlobalOptions } from "./types.ts";
import { SEP, colors, log, path, yamlParse, yamlStringify } from "./deps.ts";
import {
SEP,
colors,
log,
path,
yamlParseFile,
yamlStringify,
} from "./deps.ts";
import {
ScriptMetadata,
defaultScriptMetadata,
@@ -119,9 +126,9 @@ export async function generateFlowLockInternal(
return remote_path;
}
const flowValue = yamlParse(
await Deno.readTextFile(folder! + SEP + "flow.yaml")
) as FlowFile;
const flowValue = (await yamlParseFile(
folder! + SEP + "flow.yaml"
)) as FlowFile;
if (!justUpdateMetadataLock) {
const changedScripts = [];
@@ -798,7 +805,7 @@ export async function parseMetadataFile(
try {
metadataFilePath = scriptPath + ".script.yaml";
await Deno.stat(metadataFilePath);
const payload: any = yamlParse(await Deno.readTextFile(metadataFilePath));
const payload: any = await yamlParseFile(metadataFilePath);
replaceLock(payload);
return {
@@ -840,9 +847,9 @@ export async function parseMetadataFile(
codebases,
false
);
scriptInitialMetadata = yamlParse(
await Deno.readTextFile(metadataFilePath)
) as ScriptMetadata;
scriptInitialMetadata = (await yamlParseFile(
metadataFilePath
)) as ScriptMetadata;
replaceLock(scriptInitialMetadata);
} catch (e) {
log.info(
@@ -868,8 +875,7 @@ interface Lock {
const WMILL_LOCKFILE = "wmill-lock.yaml";
export async function readLockfile(): Promise<Lock> {
try {
const lockfile = await Deno.readTextFile(WMILL_LOCKFILE);
const read = yamlParse(lockfile);
const read = await yamlParseFile(WMILL_LOCKFILE);
if (typeof read == "object") {
return read as Lock;
} else {

View File

@@ -1,7 +1,7 @@
import { yamlStringify } from "./deps.ts";
import { Confirm } from "./deps.ts";
import { colors } from "./deps.ts";
import { yamlParse } from "./deps.ts";
import { yamlParseFile } from "./deps.ts";
import { log } from "./deps.ts";
import { compareInstanceObjects } from "./instance.ts";
import { isSuperset } from "./types.ts";
@@ -259,17 +259,24 @@ export async function pushWorkspaceKey(
}
}
export async function readInstanceSettings() {
let localSettings: GlobalSetting[] = [];
try {
localSettings = (await yamlParseFile(
"instance_settings.yaml"
)) as GlobalSetting[];
} catch {
log.warn("No instance_settings.yaml found");
}
return localSettings;
}
export async function pullInstanceSettings(preview = false) {
const remoteSettings = await wmill.listGlobalSettings();
if (preview) {
let localSettings: GlobalSetting[] = [];
try {
localSettings = yamlParse(
await Deno.readTextFile("instance_settings.yaml")
) as GlobalSetting[];
} catch {}
const localSettings: GlobalSetting[] = await readInstanceSettings();
return compareInstanceObjects(
remoteSettings,
@@ -294,9 +301,7 @@ export async function pushInstanceSettings(
baseUrl?: string
) {
const remoteSettings = await wmill.listGlobalSettings();
let localSettings = (await Deno.readTextFile("instance_settings.yaml")
.then((raw) => yamlParse(raw))
.catch(() => [])) as GlobalSetting[];
let localSettings: GlobalSetting[] = await readInstanceSettings();
if (baseUrl) {
localSettings = localSettings.filter((s) => s.name !== "base_url");
@@ -354,6 +359,17 @@ export async function pushInstanceSettings(
}
}
export async function readLocalConfigs() {
let localConfigs: Config[] = [];
try {
localConfigs = (await yamlParseFile("instance_configs.yaml")) as Config[];
} catch {
log.warn("No instance_configs.yaml found");
}
return localConfigs;
}
export async function pullInstanceConfigs(preview = false) {
const remoteConfigs = (await wmill.listConfigs()).map((x) => {
return {
@@ -363,12 +379,7 @@ export async function pullInstanceConfigs(preview = false) {
});
if (preview) {
let localConfigs: Config[] = [];
try {
localConfigs = yamlParse(
await Deno.readTextFile("instance_configs.yaml")
) as Config[];
} catch {}
const localConfigs: Config[] = await readLocalConfigs();
return compareInstanceObjects(
remoteConfigs,
@@ -395,9 +406,7 @@ export async function pushInstanceConfigs(preview: boolean = false) {
name: removeWorkerPrefix(x.name),
};
});
const localConfigs = (await Deno.readTextFile("instance_configs.yaml")
.then((raw) => yamlParse(raw))
.catch(() => [])) as Config[];
const localConfigs = await readLocalConfigs();
if (preview) {
return compareInstanceObjects(
@@ -415,7 +424,9 @@ export async function pushInstanceConfigs(preview: boolean = false) {
}
try {
await wmill.updateConfig({
name: config.name.startsWith('worker__') ? config.name : `worker__${config.name}`,
name: config.name.startsWith("worker__")
? config.name
: `worker__${config.name}`,
requestBody: config.config,
});
} catch (err) {

View File

@@ -9,7 +9,7 @@ import {
path,
log,
yamlStringify,
yamlParse,
yamlParseContent,
SEP,
} from "./deps.ts";
import * as wmill from "./gen/services.gen.ts";
@@ -112,7 +112,7 @@ async function addCodebaseDigestIfRelevant(
if (isTs) {
const c = findCodebase(path, codebases);
if (c) {
const parsed: any = yamlParse(content);
const parsed: any = yamlParseContent(path, content);
if (parsed && typeof parsed == "object") {
parsed["codebase"] = c.digest;
parsed["lock"] = undefined;
@@ -660,7 +660,7 @@ export async function elementsToMap(
if (json) {
o = JSON.parse(content);
} else {
o = yamlParse(content);
o = yamlParseContent(path, content);
}
if (o["is_secret"]) {
continue;
@@ -704,7 +704,7 @@ async function compareDynFSElement(
function parseYaml(k: string, v: string) {
if (k.endsWith(".script.yaml")) {
const o: any = yamlParse(v);
const o: any = yamlParseContent(k, v);
if (typeof o == "object") {
if (Array.isArray(o?.["lock"])) {
o["lock"] = o["lock"].join("\n");
@@ -715,7 +715,7 @@ async function compareDynFSElement(
}
return o;
} else if (k.endsWith(".app.yaml")) {
const o: any = yamlParse(v);
const o: any = yamlParseContent(k, v);
const o2 = o["policy"];
if (typeof o2 == "object") {
@@ -728,7 +728,7 @@ async function compareDynFSElement(
}
return o;
} else {
return yamlParse(v);
return yamlParseContent(k, v);
}
}
for (const [k, v] of Object.entries(m1)) {

View File

@@ -6,7 +6,7 @@ import {
colors,
log,
path,
yamlParse,
yamlParseContent,
yamlStringify,
} from "./deps.ts";
import { pushApp } from "./apps.ts";
@@ -115,7 +115,7 @@ export async function pushObj(
newObj: any,
plainSecrets: boolean,
alreadySynced: string[],
message?: string,
message?: string
) {
const typeEnding = getTypeStrFromPath(p);
@@ -155,7 +155,7 @@ export async function pushObj(
export function parseFromPath(p: string, content: string): any {
return p.endsWith(".yaml")
? yamlParse(content)
? yamlParseContent(p, content)
: p.endsWith(".json")
? JSON.parse(content)
: content;
@@ -164,7 +164,7 @@ export function parseFromFile(p: string): any {
if (p.endsWith(".json")) {
return JSON.parse(Deno.readTextFileSync(p));
} else if (p.endsWith(".yaml") || p.endsWith(".yml")) {
return yamlParse(Deno.readTextFileSync(p));
return yamlParseContent(p, Deno.readTextFileSync(p));
} else {
throw new Error("Could not read file " + p);
}
@@ -227,7 +227,7 @@ export function getTypeStrFromPath(
return typeEnding;
} else {
if (isFileResource(p)) {
return "resource"
return "resource";
}
throw new Error("Could not infer type of path " + JSON.stringify(parsed));
}

View File

@@ -13,7 +13,7 @@ import {
log,
Table,
yamlStringify,
yamlParse,
yamlParseFile,
} from "./deps.ts";
import * as wmill from "./gen/services.gen.ts";
import {
@@ -170,6 +170,7 @@ export async function pushWorkspaceUser(
},
});
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -189,6 +190,7 @@ export async function pushWorkspaceUser(
},
});
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -251,6 +253,7 @@ export async function pushGroup(
},
});
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -301,6 +304,7 @@ export async function pushGroup(
});
}
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -329,6 +333,7 @@ export async function pushGroup(
},
});
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -368,11 +373,13 @@ export async function pushGroup(
});
}
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
}
} catch (e) {
//@ts-ignore
console.error(e.body);
throw e;
}
@@ -383,11 +390,7 @@ export async function pullInstanceUsers(preview: boolean = false) {
const remoteUsers = await wmill.globalUsersExport();
if (preview) {
let localUsers: ExportedUser[] = [];
try {
const raw = await Deno.readTextFile("instance_users.yaml");
localUsers = yamlParse(raw) as ExportedUser[];
} catch {}
const localUsers: ExportedUser[] = await readInstanceUsers();
return compareInstanceObjects(remoteUsers, localUsers, "email", "user");
} else {
log.info("Pulling users from instance...");
@@ -399,11 +402,31 @@ export async function pullInstanceUsers(preview: boolean = false) {
}
}
export async function readInstanceUsers() {
let localUsers: ExportedUser[] = [];
try {
localUsers = (await yamlParseFile("instance_users.yaml")) as ExportedUser[];
} catch {
log.warn("No instance_users.yaml file found");
}
return localUsers;
}
export async function readInstanceGroups() {
let localGroups: InstanceGroup[] = [];
try {
localGroups = (await yamlParseFile(
"instance_groups.yaml"
)) as ExportedInstanceGroup[];
} catch {
log.warn("No instance_groups.yaml file found");
}
return localGroups;
}
export async function pushInstanceUsers(preview: boolean = false) {
const remoteUsers = await wmill.globalUsersExport();
const localUsers = (await Deno.readTextFile("instance_users.yaml")
.then((raw) => yamlParse(raw))
.catch(() => [])) as ExportedUser[];
const localUsers: ExportedUser[] = await readInstanceUsers();
if (preview) {
return compareInstanceObjects(localUsers, remoteUsers, "email", "user");
@@ -421,11 +444,7 @@ export async function pullInstanceGroups(preview = false) {
const remoteGroups = await wmill.exportInstanceGroups();
if (preview) {
let localGroups: InstanceGroup[] = [];
try {
const raw = await Deno.readTextFile("instance_groups.yaml");
localGroups = yamlParse(raw) as InstanceGroup[];
} catch {}
const localGroups = await readInstanceGroups();
return compareInstanceObjects(remoteGroups, localGroups, "name", "group");
} else {
log.info("Pulling groups from instance...");
@@ -441,9 +460,7 @@ export async function pullInstanceGroups(preview = false) {
export async function pushInstanceGroups(preview: boolean = false) {
const remoteGroups = await wmill.exportInstanceGroups();
const localGroups = (await Deno.readTextFile("instance_groups.yaml")
.then((raw) => yamlParse(raw))
.catch(() => [])) as ExportedInstanceGroup[];
const localGroups = await readInstanceGroups();
if (preview) {
return compareInstanceObjects(localGroups, remoteGroups, "name", "group");

View File

@@ -55,7 +55,10 @@ export function deepEqual<T>(a: T, b: T): boolean {
if (a.valueOf !== Object.prototype.valueOf) {
return a.valueOf() === b.valueOf();
}
if (a.toString !== Object.prototype.toString) {
if (
a.toString !== Object.prototype.toString &&
typeof a.toString == "function"
) {
return a.toString() === b.toString();
}

View File

@@ -34,10 +34,10 @@ export async function allWorkspaces(): Promise<Workspace[]> {
}
async function getActiveWorkspaceName(
opts: GlobalOptions
opts: GlobalOptions | undefined
): Promise<string | undefined> {
if (opts.workspace) {
return opts.workspace;
if (opts?.workspace) {
return opts?.workspace;
}
try {
return await Deno.readTextFile((await getRootStore()) + "/activeWorkspace");
@@ -47,7 +47,7 @@ async function getActiveWorkspaceName(
}
export async function getActiveWorkspace(
opts: GlobalOptions
opts: GlobalOptions | undefined
): Promise<Workspace | undefined> {
const name = await getActiveWorkspaceName(opts);
if (!name) {
@@ -115,7 +115,12 @@ async function switchC(opts: GlobalOptions, workspaceName: string) {
return;
}
return await Deno.writeTextFile(
await setActiveWorkspace(workspaceName);
return;
}
export async function setActiveWorkspace(workspaceName: string) {
await Deno.writeTextFile(
(await getRootStore()) + "/activeWorkspace",
workspaceName
);
@@ -241,10 +246,8 @@ export async function add(
},
opts
);
await Deno.writeTextFile(
(await getRootStore()) + "/activeWorkspace",
workspaceName
);
await setActiveWorkspace(workspaceName);
log.info(
colors.green.underline(
`Added workspace ${workspaceName} for ${workspaceId} on ${remote}!`

View File

@@ -4,11 +4,13 @@ ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm
FROM ${RUST_IMAGE} AS rust_base
RUN yum install -y rust-toolset
RUN yum update -y && \
yum install -y git openssl-devel npm nodejs rustfmt
# Install rust manually
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef --version ^0.1
WORKDIR /windmill
@@ -35,7 +37,7 @@ COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser
COPY /typescript-client/docs/ /frontend/static/tsdocs/
RUN npm run generate-backend-client
ENV NODE_OPTIONS "--max-old-space-size=8192"
ENV NODE_OPTIONS "--max-old-space-size=10240"
RUN npm run build

View File

@@ -3,17 +3,16 @@
This directory contains the Dockerfiles for building Windmill binaries for Red Hat Linux 9.
We build Windmill on the Red Hat Universal Base Image 9. Windmill requires the xmlsec1-devel package which is not available in the default UBI9 repositories. It is however included in the CodeReady Builder for RHEL9 repository which requires a RedHat subscription.
Moreover, only rust v1.75 is supported on Red Hat Linux 9. To make Windmill compatible with Rust v1.75, you need to pin the following libraries:
```
aws-config = "=1.4.0"
aws-sdk-sts = "=1.25.0"
aws-sdk-ssooidc = "=1.25.0"
aws-sdk-sso = "=1.25.0"
```
Make sure to include `aws-sdk-ssooidc` and `aws-sdk-sso` in the Cargo.toml of windmill-common as well to enforce the correct versions of the nested dependencies. Make them optional and include them in the `parquet` feature.
It's also possible that you need to add `#[async_recursion]` to the `lock_modules` function in the `backend/windmill-worker/src/worker_lockfiles.rs` file for it to compile.
Once the image is built, you can simply copy the binary on any Red Hat Linux 9 machine and run it. You will just need to install the xmlsec1 package which can be installed directly using `yum/dnf install xmlsec1`.
## Notes
- you will need to register on Red Hat and have an individual developer subscription and pass the username and password to docker build:
```
docker build \
-f docker/RHEL9/Dockerfile \
--build-arg features="$features" \
--secret id=rh_username,src=/path/to/rh_username \
--secret id=rh_password,src=/path/to/rh_password \
.
```

View File

@@ -1,28 +0,0 @@
Error parsing /git/windmill/backend/windmill-api/openapi.yaml: duplicated mapping key (8350:3)
8347 | application/json:
8348 | schema: {}
8349 |
8350 | /w/{workspace}/job_helpers/loa ...
----------^
8351 | get:
8352 | summary: Load a preview of ...
ParserError: Error parsing /git/windmill/backend/windmill-api/openapi.yaml: duplicated mapping key (8350:3)
8347 | application/json:
8348 | schema: {}
8349 |
8350 | /w/{workspace}/job_helpers/loa ...
----------^
8351 | get:
8352 | summary: Load a preview of ...
at Object.parse (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/parsers/yaml.js:44:23)
at getResult (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/util/plugins.js:116:22)
at runNextPlugin (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/util/plugins.js:64:32)
at /git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/util/plugins.js:55:9
at new Promise (<anonymous>)
at Object.run (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/util/plugins.js:54:12)
at parseFile (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/parse.js:130:38)
at parse (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/parse.js:56:30)
at async $RefParser.parse (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/index.js:115:28)
at async $RefParser.resolve (/git/windmill/frontend/node_modules/@apidevtools/json-schema-ref-parser/dist/lib/index.js:145:13)

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.405.5",
"version": "1.409.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.405.5",
"version": "1.409.2",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.405.5",
"version": "1.409.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -300,6 +300,14 @@
"types": "./package/utils.d.ts",
"default": "./package/utils.js"
},
"./components/icons/store": {
"types": "./package/components/icons/store.d.ts",
"default": "./package/components/icons/store.js"
},
"./script_helpers": {
"types": "./package/script_helpers.d.ts",
"default": "./package/script_helpers.js"
},
"./infer": {
"types": "./package/infer.d.ts",
"default": "./package/infer.js"
@@ -341,6 +349,11 @@
"types": "./package/components/DropdownV2.svelte.d.ts",
"svelte": "./package/components/DropdownV2.svelte",
"default": "./package/components/DropdownV2.svelte"
},
"./components/flows/FlowHistoryInner.svelte": {
"types": "./package/components/flows/FlowHistoryInner.svelte.d.ts",
"svelte": "./package/components/flows/FlowHistoryInner.svelte",
"default": "./package/components/flows/FlowHistoryInner.svelte"
}
},
"files": [
@@ -426,6 +439,9 @@
"components/EditableSchemaWrapper.svelte": [
"./package/components/schema/EditableSchemaWrapper.svelte.d.ts"
],
"components/flows/FlowHistoryInner.svelte": [
"./package/components/flows/FlowHistoryInner.svelte.d.ts"
],
"utils": [
"./package/utils.d.ts"
],
@@ -461,6 +477,12 @@
],
"components/DropdownV2.svelte": [
"./package/components/DropdownV2.svelte.d.ts"
],
"script_helpers": [
"./package/script_helpers.d.ts"
],
"components/icons/store": [
"./package/components/icons/store.d.ts"
]
}
},

View File

@@ -145,3 +145,23 @@ svelte-virtual-list-contents > * + * {
rgba(255, 69, 58, 0.2) 20px
);
}
.bg-draggedover {
background-image: repeating-linear-gradient(
-45deg,
rgba(0, 0, 128, 0.2),
rgba(0, 0, 192, 0.2) 10px,
rgba(0, 0, 128, 0.2) 10px,
rgba(0, 0, 192, 0.2) 20px
);
}
.bg-draggedover-dark {
background-image: repeating-linear-gradient(
-45deg,
rgba(0, 0, 128, 0.6),
rgba(0, 0, 192, 0.6) 10px,
rgba(0, 0, 128, 0.6) 10px,
rgba(0, 0, 192, 0.6) 20px
);
}

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import AppConnectInner from '$lib/components/AppConnectInner.svelte'
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
import { Button } from '$lib/components/common'
import { workspaceStore } from '$lib/stores'
import { onMount } from 'svelte'
export let resourceType: string | undefined = undefined
export let workspace: string
export let express = false
let step = 1
let disabled = false
let manual = true
let appConnect: AppConnectInner | undefined = undefined
let darkMode: boolean = false
if (workspace) {
$workspaceStore = workspace
}
onMount(async () => {
if (resourceType) {
appConnect?.open(resourceType, express)
}
})
</script>
<DarkModeObserver bind:darkMode />
<div>
<div class="flex flex-row-reverse w-full">
<div class="flex gap-2">
{#if step > 2}
<Button variant="border" on:click={appConnect?.back}>Back</Button>
{/if}
<Button {disabled} on:click={appConnect?.next}>
{#if step == 2 && !manual}
Connect
{:else if step == 1}
Next
{:else}
Save
{/if}
</Button>
</div>
</div>
<AppConnectInner
bind:this={appConnect}
bind:step
bind:resourceType
bind:disabled
bind:manual
on:error
on:refresh
/>
</div>

View File

@@ -89,6 +89,7 @@
export let editor: SimpleEditor | undefined = undefined
export let orderEditable = false
export let shouldDispatchChanges: boolean = false
export let noDefaultOnSelectFirst: boolean = false
export let helperScript:
| { type: 'inline'; path?: string; lang: Script['language']; code: string }
| { type: 'hash'; hash: string }
@@ -140,6 +141,9 @@
defaultValue?: any,
nnullable?: boolean
) {
if (label == 'toString' && typeof value == 'function') {
value = undefined
}
if ((value == undefined || value == null) && !ignoreValueUndefined) {
value = defaultValue
if (defaultValue === undefined || defaultValue === null) {
@@ -537,11 +541,14 @@
{:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format.split('-').length > 1 && resourceTypes.includes(format.substring('resource-'.length))))}
<ObjectResourceInput
{defaultValue}
selectFirst
selectFirst={!noDefaultOnSelectFirst}
{disablePortal}
{format}
bind:value
bind:editor
on:clear={() => {
defaultValue = null
}}
{showSchemaExplorer}
/>
{:else if inputCat == 'resource-object' && format.split('-').length > 1 && format
@@ -807,7 +814,7 @@
</div>
{:else if inputCat == 'resource-string'}
<ResourcePicker
selectFirst
selectFirst={noDefaultOnSelectFirst}
{disablePortal}
bind:value
initialValue={defaultValue}

View File

@@ -6,6 +6,7 @@
import AssignableTagsInner from './AssignableTagsInner.svelte'
export let placement: 'bottom-end' | 'top-end' = 'bottom-end'
export let color: 'nord' | 'dark' = 'dark'
</script>
<Popup
@@ -13,7 +14,7 @@
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Button color="dark" size="xs" nonCaptureEvent={true}>
<Button {color} size="xs" nonCaptureEvent={true}>
<div class="flex flex-row gap-1 items-center"
><Pen size={14} /> Custom Tags&nbsp;<Tooltip light
>Tags are assigned to scripts and flows. Workers only accept jobs that correspond to their

View File

@@ -52,6 +52,10 @@
>
<input type="text" placeholder="yourorg" bind:value={org} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Custom Name</span>
<input type="text" placeholder="Custom Name" bind:value={value['display_name']} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Id</span>
<input type="text" placeholder="Client Id" bind:value={value['id']} />

View File

@@ -52,6 +52,10 @@
>
<input type="text" placeholder="yourorg" bind:value={org} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Custom Name</span>
<input type="text" placeholder="Custom Name" bind:value={value['display_name']} />
</label>
<label class="block pb-2">
<span class="text-primary font-semibold text-sm">Client Id</span>
<input type="text" placeholder="Client Id" bind:value={value['id']} />

View File

@@ -203,7 +203,7 @@
<div class="w-full flex space-x-8">
<div class="w-full flex flex-col gap-4">
<Label label="Cron">
<Label label="Cron" class="font-semibold" primary={true}>
<svelte:fragment slot="error">
{#if !validCRON}
<div class="text-red-600 text-xs"> Invalid cron syntax </div>
@@ -219,10 +219,10 @@
{disabled}
/>
</Label>
<Label label="Timezone">
<Label label="Timezone" class="font-semibold" primary>
{#if disabled}
<div>
<Badge>{timezone}</Badge>
<Badge><span class="text-primary dark:text-primary-inverse">{timezone}</span></Badge>
</div>
{:else}
<Select
@@ -390,9 +390,8 @@
</div>
<div class="w-full flex flex-col space-y-2">
<div class="text-sm font-semibold leading-none">Execution summary</div>
<div class="text-sm font-semibold leading-none">Estimated upcoming events ({timezone})</div>
<div class="flex flex-col space-y-2">
<div class="text-sm">Estimated upcoming events ({timezone})</div>
<div class="flex flex-col rounded-md p-4 border text-tertiary bg-surface-secondary gap-0.5">
{#each preview as date}
<span class="text-sm">{dateFormatter(new Date(date))}</span>

View File

@@ -67,7 +67,7 @@
</button>
{/if}
{#if showTooltip && !disablePopup}
<Portal>
<Portal name="custom-popover">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
use:popperContent={popperOptions}

View File

@@ -15,7 +15,8 @@
WorkspaceService,
type InputTransform,
type RawScript,
type PathScript
type PathScript,
type TriggersCount
} from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { copilotInfo, userStore, workspaceStore } from '$lib/stores'
@@ -41,14 +42,13 @@
import { workspacedOpenai } from './copilot/lib'
import type { FlowCopilotContext, FlowCopilotModule } from './copilot/flow'
import { pickScript } from './flows/flowStateUtils'
import type { Schedule } from './flows/scheduleUtils'
import {
approximateFindPythonRelativePath,
isTypescriptRelativePath,
parseTypescriptDeps
} from '$lib/relative_imports'
import Tooltip from './Tooltip.svelte'
import type { ScheduleTrigger, TriggerContext } from './triggers'
$: token = $page.url.searchParams.get('wm_token') ?? undefined
$: workspace = $page.url.searchParams.get('workspace') ?? undefined
$: themeDarkRaw = $page.url.searchParams.get('activeColorTheme')
@@ -467,13 +467,7 @@
}
const flowStateStore = writable({} as FlowState)
const scheduleStore = writable<Schedule>({
args: {},
cron: '',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
enabled: false,
summary: undefined
})
const previewArgsStore = writable<Record<string, any>>({})
const scriptEditorDrawer = writable(undefined)
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
@@ -481,10 +475,19 @@
const testStepStore = writable<Record<string, any>>({})
const selectedIdStore = writable('settings-metadata')
const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>(
'webhooks'
)
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(undefined)
const triggersCount = writable<TriggersCount | undefined>(undefined)
setContext<TriggerContext>('TriggerContext', {
primarySchedule: primaryScheduleStore,
selectedTrigger: selectedTriggerStore,
triggersCount: triggersCount
})
setContext<FlowEditorContext>('FlowEditorContext', {
selectedId: selectedIdStore,
schedule: scheduleStore,
previewArgs: previewArgsStore,
scriptEditorDrawer,
moving,

View File

@@ -559,6 +559,11 @@
<pre class="text-xs pt-2 whitespace-pre-wrap text-primary"
>{result.error.stack ?? ''}</pre
>
{#if result.error?.extra}
<pre class="text-xs pt-2 whitespace-pre-wrap text-primary"
>{JSON.stringify(result.error.extra, null, 4)}</pre
>
{/if}
<slot />
</div>
{#if language === 'bun'}
@@ -627,7 +632,7 @@
>
</button>
{:else if !result?.disable_download}
<FileDownload s3object={result} />
<FileDownload {workspaceId} s3object={result} />
<button
class="text-secondary underline text-2xs whitespace-nowrap"
on:click={() => {
@@ -854,7 +859,7 @@
</DrawerContent>
</Drawer>
<Portal>
<Portal name="s3filepicker">
<S3FilePicker bind:this={s3FileViewer} readOnlyMode={true} />
</Portal>
{/if}

View File

@@ -9,7 +9,8 @@
ScriptService,
type OpenFlow,
type RawScript,
type InputTransform
type InputTransform,
type TriggersCount
} from '$lib/gen'
import { initHistory, push, redo, undo } from '$lib/history'
import {
@@ -40,7 +41,6 @@
import { dfs, getPreviousIds } from './flows/previousResults'
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
import type { FlowEditorContext, FlowInput } from './flows/types'
import { cleanInputs, emptyFlowModuleState } from './flows/utils'
import {
@@ -82,6 +82,7 @@
import Summary from './Summary.svelte'
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
import FlowYamlEditor from './flows/header/FlowYamlEditor.svelte'
import { type TriggerContext, type ScheduleTrigger } from './triggers'
export let initialPath: string = ''
export let pathStoreInit: string | undefined = undefined
@@ -99,30 +100,48 @@
export let diffDrawer: DiffDrawer | undefined = undefined
export let customUi: FlowBuilderWhitelabelCustomUi = {}
export let disableAi: boolean = false
export let disabledFlowInputs = false
export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined
$: setContext('customUi', customUi)
const dispatch = createEventDispatcher()
async function createSchedule(path: string) {
const { cron, timezone, args, enabled, summary } = $scheduleStore
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(savedPrimarySchedule)
const triggersCount = writable<TriggersCount | undefined>(
savedPrimarySchedule
? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } }
: undefined
)
try {
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
requestBody: {
path: path,
schedule: formatCron(cron),
timezone,
script_path: path,
is_flow: true,
args,
enabled,
summary
}
})
} catch (err) {
sendUserToast(`The primary schedule could not be created: ${err}`, true)
export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) {
primaryScheduleStore.set(schedule)
loadTriggers()
}
async function createSchedule(path: string) {
if ($primaryScheduleStore) {
const { cron, timezone, args, enabled, summary } = $primaryScheduleStore
try {
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
requestBody: {
path: path,
schedule: formatCron(cron),
timezone,
script_path: path,
is_flow: true,
args,
enabled,
summary
}
})
} catch (err) {
sendUserToast(`The primary schedule could not be created: ${err}`, true)
}
} else {
sendUserToast('The primary schedule could not be created: no schedule data', true)
}
}
@@ -186,7 +205,8 @@
typ: 'flow',
value: {
...flow,
path: $pathStore
path: $pathStore,
primary_schedule: $primaryScheduleStore
}
}
})
@@ -240,7 +260,7 @@
// console.log('flow', computeUnlockedSteps(flow)) // del
// loadingSave = false // del
// return
const { cron, timezone, args, enabled, summary } = $scheduleStore
if (newFlow) {
try {
localStorage.removeItem('flow')
@@ -263,7 +283,7 @@
deployment_message: deploymentMsg || undefined
}
})
if (enabled) {
if ($primaryScheduleStore && $primaryScheduleStore.enabled) {
await createSchedule($pathStore)
}
} else {
@@ -283,31 +303,40 @@
workspace: $workspaceStore ?? '',
path: initialPath
})
if (
JSON.stringify(schedule.args) != JSON.stringify(args) ||
schedule.schedule != cron ||
schedule.timezone != timezone ||
schedule.summary != summary
) {
await ScheduleService.updateSchedule({
if ($primaryScheduleStore) {
const { cron, timezone, args, enabled, summary } = $primaryScheduleStore
if (
JSON.stringify(schedule.args) != JSON.stringify(args) ||
schedule.schedule != cron ||
schedule.timezone != timezone ||
schedule.summary != summary
) {
await ScheduleService.updateSchedule({
workspace: $workspaceStore ?? '',
path: initialPath,
requestBody: {
schedule: formatCron(cron),
timezone,
args,
summary
}
})
}
if (enabled != schedule.enabled) {
await ScheduleService.setScheduleEnabled({
workspace: $workspaceStore ?? '',
path: initialPath,
requestBody: { enabled }
})
}
} else if (scheduleExists) {
await ScheduleService.deleteSchedule({
workspace: $workspaceStore ?? '',
path: initialPath,
requestBody: {
schedule: formatCron(cron),
timezone,
args,
summary
}
path: $pathStore
})
}
if (enabled != schedule.enabled) {
await ScheduleService.setScheduleEnabled({
workspace: $workspaceStore ?? '',
path: initialPath,
requestBody: { enabled }
})
}
} else if (enabled) {
} else if ($primaryScheduleStore && $primaryScheduleStore.enabled) {
await createSchedule(initialPath)
}
@@ -358,7 +387,8 @@
encodeState({
flow: $flowStore,
path: $pathStore,
selectedId: $selectedIdStore
selectedId: $selectedIdStore,
primarySchedule: $primaryScheduleStore
})
)
} catch (err) {
@@ -368,18 +398,14 @@
}
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>(
'webhooks'
)
export function getSelectedId() {
return $selectedIdStore
}
const scheduleStore = writable<Schedule>({
summary: undefined,
args: {},
cron: '',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
enabled: false
})
const previewArgsStore = writable<Record<string, any>>(initialArgs)
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
@@ -394,10 +420,14 @@
selectedIdStore.set(selectedId)
}
function selectTrigger(selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes') {
selectedTriggerStore.set(selectedTrigger)
}
let insertButtonOpen = writable<boolean>(false)
setContext<FlowEditorContext>('FlowEditorContext', {
selectedId: selectedIdStore,
schedule: scheduleStore,
previewArgs: previewArgsStore,
scriptEditorDrawer,
moving,
@@ -413,25 +443,31 @@
insertButtonOpen
})
async function loadSchedule() {
loadFlowSchedule(initialPath, $workspaceStore!)
.then((schedule: Schedule) => {
scheduleStore.set(schedule)
})
.catch(() => {
scheduleStore.set({
summary: undefined,
cron: '0 */5 * * *',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
args: {},
enabled: false
})
})
setContext<TriggerContext>('TriggerContext', {
selectedTrigger: selectedTriggerStore,
primarySchedule: primaryScheduleStore,
triggersCount
})
async function loadTriggers() {
$triggersCount = await FlowService.getTriggersCountOfFlow({
workspace: $workspaceStore!,
path: initialPath
})
if ($primaryScheduleStore && $triggersCount.primary_schedule == undefined) {
$triggersCount = {
...($triggersCount ?? {}),
schedule_count: ($triggersCount.schedule_count ?? 0) + 1,
primary_schedule: {
schedule: $primaryScheduleStore.cron
}
}
}
}
$: selectedId && select(selectedId)
$: initialPath && initialPath != '' && $workspaceStore && loadSchedule()
$: initialPath && initialPath != '' && $workspaceStore && loadTriggers()
function onKeyDown(event: KeyboardEvent) {
let classes = event.target?.['className']
@@ -698,10 +734,13 @@
}
if (module.type === 'trigger') {
if (!$scheduleStore.cron) {
$scheduleStore.cron = '0 */15 * * *'
$primaryScheduleStore = {
summary: 'Scheduled poll of flow',
args: {},
cron: '0 */15 * * *',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
enabled: true
}
$scheduleStore.enabled = true
}
const flowModule: FlowModule & {
@@ -1151,7 +1190,7 @@
</div>
<div class="gap-4 flex-row hidden md:flex w-full max-w-md">
{#if $scheduleStore.enabled}
{#if $primaryScheduleStore != undefined ? $primaryScheduleStore && $primaryScheduleStore?.enabled : $triggersCount?.primary_schedule}
<Button
btnClasses="hidden lg:inline-flex"
startIcon={{ icon: Calendar }}
@@ -1159,10 +1198,15 @@
color="light"
size="xs"
on:click={async () => {
select('settings-schedule')
select('triggers')
selectTrigger('schedules')
}}
>
{$scheduleStore.cron ?? ''}
{$primaryScheduleStore != undefined
? $primaryScheduleStore
? $primaryScheduleStore?.cron
: ''
: $triggersCount?.primary_schedule?.schedule}
</Button>
{/if}
@@ -1326,12 +1370,14 @@
<!-- metadata -->
{#if $flowStateStore}
<FlowEditor
{disabledFlowInputs}
disableAi={disableAi || customUi?.stepInputs?.ai == false}
disableSettings={customUi?.settingsPanel === false}
{loading}
on:reload={() => {
renderCount += 1
}}
{newFlow}
/>
{:else}
<CenteredPage>Loading...</CenteredPage>

View File

@@ -13,6 +13,7 @@
description?: string
value: FlowValue
schema?: any
path?: string
}
export let overflowAuto = false
@@ -32,20 +33,25 @@
class:overflow-auto={overflowAuto}
>
<FlowGraphV2
path={flow?.path}
{download}
minHeight={400}
modules={flow?.value?.modules}
failureModule={flow?.value?.failure_module}
preprocessorModule={flow?.value?.preprocessor_module}
on:select={(e) => {
if (e?.detail === 'failure') {
let nodeId = e?.detail
if (nodeId === 'triggers') {
dispatch('triggerDetail')
return
} else if (nodeId === 'failure') {
stepDetail = flow?.value?.failure_module
} else if (e?.detail === 'preprocessor') {
} else if (nodeId === 'preprocessor') {
stepDetail = flow?.value?.preprocessor_module
} else {
stepDetail = dfs(flow?.value?.modules ?? [], (m) => m).find((m) => m?.id === e?.detail)
}
stepDetail = stepDetail ?? e?.detail
stepDetail = stepDetail ?? nodeId
dispatch('select', stepDetail)
}}
/>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import FieldHeader from './FieldHeader.svelte'
export let schema: Schema | { [key: string]: unknown } | undefined
</script>
<ul class="my-2">
{#if schema == undefined}
<li class="text-secondary text-xs italic mb-4">No inputs</li>
{:else}
{#each Object.entries(schema.properties ?? {}) as [inp, v]}
<li class="list-disc flex flex-row items-center">
<FieldHeader
label={inp}
required={Array.isArray(schema.required) && schema.required?.includes(inp)}
type={v?.type}
contentEncoding={v?.contentEncoding}
format={v?.format}
/><span class="ml-4 mt-1 text-xs"
>{v?.default != undefined && v?.default != ''
? 'default: ' + JSON.stringify(v?.default)
: ''}</span
>
</li>
{/each}
{/if}
</ul>

Some files were not shown because too many files have changed in this diff Show More