Compare commits
74 Commits
glm/improv
...
v1.409.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45bf59ba32 | ||
|
|
1466da3999 | ||
|
|
3174024d8e | ||
|
|
4e6c0d1e80 | ||
|
|
6abb346013 | ||
|
|
79100f8d0a | ||
|
|
28f6f6b067 | ||
|
|
d24e153065 | ||
|
|
8df2cec40a | ||
|
|
a8c4ea2334 | ||
|
|
20e8be15fe | ||
|
|
c3b536b1b8 | ||
|
|
c06c42a1ed | ||
|
|
01b95ebd8d | ||
|
|
99814c7ab0 | ||
|
|
10414df4a7 | ||
|
|
91a3d06529 | ||
|
|
f38b3d14e8 | ||
|
|
e736572db1 | ||
|
|
7126ba12c7 | ||
|
|
29cb954639 | ||
|
|
411bce7e13 | ||
|
|
99c5b3ecda | ||
|
|
9eb1ecc9f3 | ||
|
|
dcdbf1afb4 | ||
|
|
34c39ce0ab | ||
|
|
288dfe7c70 | ||
|
|
8867260e43 | ||
|
|
4112eb6072 | ||
|
|
9d2cb91f9c | ||
|
|
a323d2b29a | ||
|
|
fa642c8db1 | ||
|
|
42fe31f804 | ||
|
|
242a251ab1 | ||
|
|
7a92a152d1 | ||
|
|
fad4785b0a | ||
|
|
296aa97bd8 | ||
|
|
d77412cef6 | ||
|
|
685eb5d471 | ||
|
|
fcdf82af36 | ||
|
|
d2c4d3fa20 | ||
|
|
e06c845ed4 | ||
|
|
f82f091290 | ||
|
|
9f7edbaf1a | ||
|
|
2dab068fd3 | ||
|
|
7e754a1e50 | ||
|
|
be112408e7 | ||
|
|
568cc66932 | ||
|
|
0915968eba | ||
|
|
982dde2b9d | ||
|
|
7db74eecb9 | ||
|
|
81b0ff1ad1 | ||
|
|
b133f19333 | ||
|
|
d9bdc5a5b0 | ||
|
|
ba17dfc841 | ||
|
|
7ca5bf2fae | ||
|
|
cf8d443603 | ||
|
|
0e61f8db78 | ||
|
|
c3a5736641 | ||
|
|
26b11a0015 | ||
|
|
7dbe49b77b | ||
|
|
2e9312216e | ||
|
|
7b0503332d | ||
|
|
25e417538f | ||
|
|
6280ed3524 | ||
|
|
ffaf756e9a | ||
|
|
21cd7a63ae | ||
|
|
d90d6c2b89 | ||
|
|
111bfc6a65 | ||
|
|
257f0971f8 | ||
|
|
f831b9b1fe | ||
|
|
b579db61ec | ||
|
|
e44decb9c2 | ||
|
|
644dd87c50 |
27
.github/DockerfileBackendTests
vendored
27
.github/DockerfileBackendTests
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/backend-test.yml
vendored
4
.github/workflows/backend-test.yml
vendored
@@ -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
|
||||
|
||||
128
.github/workflows/build-publish-rh-image.yml
vendored
Normal file
128
.github/workflows/build-publish-rh-image.yml
vendored
Normal 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
|
||||
12
.github/workflows/docker-image.yml
vendored
12
.github/workflows/docker-image.yml
vendored
@@ -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}}
|
||||
|
||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
14
backend/.sqlx/query-0ee63ef2dd5c88edba2a1f56d31f29876724922f148dad7af35b36efbf70207a.json
generated
Normal file
14
backend/.sqlx/query-0ee63ef2dd5c88edba2a1f56d31f29876724922f148dad7af35b36efbf70207a.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM healthchecks WHERE check_type = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0ee63ef2dd5c88edba2a1f56d31f29876724922f148dad7af35b36efbf70207a"
|
||||
}
|
||||
15
backend/.sqlx/query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json
generated
Normal file
15
backend/.sqlx/query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json
generated
Normal 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"
|
||||
}
|
||||
24
backend/.sqlx/query-1ca5bc2d35c0498b587fd0618434def64233dc4f8fc3344d8d74be8e96ded659.json
generated
Normal file
24
backend/.sqlx/query-1ca5bc2d35c0498b587fd0618434def64233dc4f8fc3344d8d74be8e96ded659.json
generated
Normal 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"
|
||||
}
|
||||
12
backend/.sqlx/query-2041526bc58872d71f91f7698144039bd67f8e37895befa94a15b7e4019e114b.json
generated
Normal file
12
backend/.sqlx/query-2041526bc58872d71f91f7698144039bd67f8e37895befa94a15b7e4019e114b.json
generated
Normal 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"
|
||||
}
|
||||
14
backend/.sqlx/query-27920aaa55666ffc14a36a247f89ff7994ee40d3953b9f772d0e0ab999bccb7b.json
generated
Normal file
14
backend/.sqlx/query-27920aaa55666ffc14a36a247f89ff7994ee40d3953b9f772d0e0ab999bccb7b.json
generated
Normal 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"
|
||||
}
|
||||
24
backend/.sqlx/query-31bc3dcea29be9cc0242771d25a232f173446d29c08fc29ddb8d55294f2c070e.json
generated
Normal file
24
backend/.sqlx/query-31bc3dcea29be9cc0242771d25a232f173446d29c08fc29ddb8d55294f2c070e.json
generated
Normal 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"
|
||||
}
|
||||
22
backend/.sqlx/query-34a45763bb4d14162f4cd3fa07cd8020f1f6085f4ee85f5eab3458637edf26cd.json
generated
Normal file
22
backend/.sqlx/query-34a45763bb4d14162f4cd3fa07cd8020f1f6085f4ee85f5eab3458637edf26cd.json
generated
Normal 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"
|
||||
}
|
||||
29
backend/.sqlx/query-3ecb25b05d6c14b499f9b00af42ae74134728899f6b59c68b246979bc5143e30.json
generated
Normal file
29
backend/.sqlx/query-3ecb25b05d6c14b499f9b00af42ae74134728899f6b59c68b246979bc5143e30.json
generated
Normal 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"
|
||||
}
|
||||
15
backend/.sqlx/query-57e270e032e8c04dda7b5c1ca949861756b3ad367a4a500728332a7cb91560a4.json
generated
Normal file
15
backend/.sqlx/query-57e270e032e8c04dda7b5c1ca949861756b3ad367a4a500728332a7cb91560a4.json
generated
Normal 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"
|
||||
}
|
||||
23
backend/.sqlx/query-5fc6b4a4dbb7875bdec76f876c18543435a95b019b20081f52f6ed6f4457e3c7.json
generated
Normal file
23
backend/.sqlx/query-5fc6b4a4dbb7875bdec76f876c18543435a95b019b20081f52f6ed6f4457e3c7.json
generated
Normal 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"
|
||||
}
|
||||
22
backend/.sqlx/query-5fd70c70ce52cbc51fa9124cb05f82b5951f17d1b7eade53c6d89253d55f8b9f.json
generated
Normal file
22
backend/.sqlx/query-5fd70c70ce52cbc51fa9124cb05f82b5951f17d1b7eade53c6d89253d55f8b9f.json
generated
Normal 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"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO windmill_migrations (name) VALUES ('bypassrls_1-2')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f"
|
||||
}
|
||||
14
backend/.sqlx/query-900ac59515e4283f4b57516210575dfe92f74a7220ed69e61899a6e0f053d9cd.json
generated
Normal file
14
backend/.sqlx/query-900ac59515e4283f4b57516210575dfe92f74a7220ed69e61899a6e0f053d9cd.json
generated
Normal 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"
|
||||
}
|
||||
23
backend/.sqlx/query-a7f5431e3b8960e9dc46fae69dd4391516d8b169186548ec44528c84078b80d8.json
generated
Normal file
23
backend/.sqlx/query-a7f5431e3b8960e9dc46fae69dd4391516d8b169186548ec44528c84078b80d8.json
generated
Normal 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"
|
||||
}
|
||||
14
backend/.sqlx/query-ad42118ccf6a9d2d1e072c4df064ddf964a5b3cd088fc162d0d8222325d4a5ea.json
generated
Normal file
14
backend/.sqlx/query-ad42118ccf6a9d2d1e072c4df064ddf964a5b3cd088fc162d0d8222325d4a5ea.json
generated
Normal 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"
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
"trigger",
|
||||
"failure",
|
||||
"command",
|
||||
"approval"
|
||||
"approval",
|
||||
"preprocessor"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
24
backend/.sqlx/query-c060b8bbc5af7d2e7d0aaff64f0f62ec9db58611a99b0ba7f0375638b128ab89.json
generated
Normal file
24
backend/.sqlx/query-c060b8bbc5af7d2e7d0aaff64f0f62ec9db58611a99b0ba7f0375638b128ab89.json
generated
Normal 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"
|
||||
}
|
||||
23
backend/.sqlx/query-c4e1873bfc7b905e7299a021f4baa2a97e95f4797c5e11f37822e19828422b7e.json
generated
Normal file
23
backend/.sqlx/query-c4e1873bfc7b905e7299a021f4baa2a97e95f4797c5e11f37822e19828422b7e.json
generated
Normal 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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
59
backend/.sqlx/query-c7ee7ce64686cef41cebd99ad7ef31572fc1bf12e6ae473fd58fafb025989965.json
generated
Normal file
59
backend/.sqlx/query-c7ee7ce64686cef41cebd99ad7ef31572fc1bf12e6ae473fd58fafb025989965.json
generated
Normal 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"
|
||||
}
|
||||
22
backend/.sqlx/query-cecf1addc4aecb087a14786b2a9165895ca61ef042947c7314f66514d7f29edc.json
generated
Normal file
22
backend/.sqlx/query-cecf1addc4aecb087a14786b2a9165895ca61ef042947c7314f66514d7f29edc.json
generated
Normal 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"
|
||||
}
|
||||
23
backend/.sqlx/query-d7a0f19f9e18d2ea49316012375ad78b69292ba091d69880945e42bebe890d66.json
generated
Normal file
23
backend/.sqlx/query-d7a0f19f9e18d2ea49316012375ad78b69292ba091d69880945e42bebe890d66.json
generated
Normal 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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
59
backend/.sqlx/query-eff32aeac25a75d06f73e08c26dd3fd25f6b85cbea870505751c6a82457ae1da.json
generated
Normal file
59
backend/.sqlx/query-eff32aeac25a75d06f73e08c26dd3fd25f6b85cbea870505751c6a82457ae1da.json
generated
Normal 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"
|
||||
}
|
||||
23
backend/.sqlx/query-f06e0e4fa358b26792df22fff48b71a6fcfa1e5603ea472892917c1accd1aafb.json
generated
Normal file
23
backend/.sqlx/query-f06e0e4fa358b26792df22fff48b71a6fcfa1e5603ea472892917c1accd1aafb.json
generated
Normal 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
742
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -1 +1 @@
|
||||
0f5f42d2f8f5f1af05c8086f3dc7ad38d83750df
|
||||
0428068e4fbbd1380a4d8bbaab5c8e7955decdb8
|
||||
|
||||
1
backend/migrations/20241006144414_admin_policy.down.sql
Normal file
1
backend/migrations/20241006144414_admin_policy.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
24
backend/migrations/20241006144414_admin_policy.up.sql
Normal file
24
backend/migrations/20241006144414_admin_policy.up.sql
Normal 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;
|
||||
$$;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Drop the alert_locks table
|
||||
DROP TABLE IF EXISTS concurrency_locks;
|
||||
@@ -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
|
||||
);
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
131
backend/windmill-api/src/triggers.rs
Normal file
131
backend/windmill-api/src/triggers.rs
Normal 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))
|
||||
}
|
||||
@@ -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?;
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -29,3 +29,4 @@ tempfile.workspace = true
|
||||
bytes.workspace = true
|
||||
object_store = { workspace = true, optional = true}
|
||||
tokio-tar.workspace = true
|
||||
lazy_static.workspace = true
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
),
|
||||
)?;
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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."
|
||||
|
||||
24
cli/deps.ts
24
cli/deps.ts
@@ -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
|
||||
|
||||
|
||||
38
cli/flow.ts
38
cli/flow.ts
@@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.405.5";
|
||||
export const VERSION = "1.409.2";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
12
cli/sync.ts
12
cli/sync.ts
@@ -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)) {
|
||||
|
||||
10
cli/types.ts
10
cli/types.ts
@@ -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));
|
||||
}
|
||||
|
||||
51
cli/user.ts
51
cli/user.ts
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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}!`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 \
|
||||
.
|
||||
```
|
||||
@@ -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)
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
|
||||
@@ -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 <Tooltip light
|
||||
>Tags are assigned to scripts and flows. Workers only accept jobs that correspond to their
|
||||
|
||||
@@ -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']} />
|
||||
|
||||
@@ -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']} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
/>
|
||||
|
||||
29
frontend/src/lib/components/FlowInputViewer.svelte
Normal file
29
frontend/src/lib/components/FlowInputViewer.svelte
Normal 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
Reference in New Issue
Block a user