Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a974e48988 | ||
|
|
e7d08359fc | ||
|
|
22c2d248b2 | ||
|
|
6ba3fda65d | ||
|
|
30b90e4951 | ||
|
|
0655803860 | ||
|
|
c288d73c13 | ||
|
|
c03c0f6172 | ||
|
|
bc512bd307 | ||
|
|
e5e03b5baa | ||
|
|
02fdd58e47 | ||
|
|
6272c9ff41 | ||
|
|
7115a35779 | ||
|
|
c0853eafc9 | ||
|
|
d56a956b9a | ||
|
|
7349d0a320 | ||
|
|
70d1e6c720 | ||
|
|
b78c3fe574 | ||
|
|
71d23db5cb | ||
|
|
832df264b4 | ||
|
|
2334802384 | ||
|
|
8ea2906d09 | ||
|
|
88d0e97ce0 | ||
|
|
d1e119e052 | ||
|
|
bf629f39cc | ||
|
|
c90fb8d293 | ||
|
|
55926f957f | ||
|
|
2eb6a7a742 | ||
|
|
a77f74cb7e | ||
|
|
413ad2c925 | ||
|
|
2c3d4920e0 | ||
|
|
0029dc11e9 | ||
|
|
dd17a6bd14 | ||
|
|
4f9193c036 | ||
|
|
843b14fe99 | ||
|
|
b60f42e387 | ||
|
|
c01e179d8f | ||
|
|
15d5c60d90 | ||
|
|
d88674a7af | ||
|
|
6e9c350b4a | ||
|
|
e7148f6723 | ||
|
|
a84c54d663 | ||
|
|
cfa20ae0d7 | ||
|
|
b54edf153e | ||
|
|
5e3a3e2103 | ||
|
|
a87f34fb4a | ||
|
|
7886f8f471 | ||
|
|
1e7de238cf | ||
|
|
9e78a51adc | ||
|
|
b9b30e66ec | ||
|
|
6749f2c136 | ||
|
|
39dc6857eb | ||
|
|
b6ab184889 | ||
|
|
5104dba631 | ||
|
|
a1c40d7fd0 | ||
|
|
7b3128171e | ||
|
|
657f03bc67 | ||
|
|
fbcac1e0a6 | ||
|
|
1c83876775 | ||
|
|
adecf4b2d1 | ||
|
|
f2f8bbe1d4 | ||
|
|
f1fa6bca84 | ||
|
|
704e75e893 | ||
|
|
d771e38bfd | ||
|
|
cc111ba7dc | ||
|
|
43a89ee1cc | ||
|
|
947dd21933 | ||
|
|
989c9e6102 | ||
|
|
811de58712 | ||
|
|
3508b6d793 | ||
|
|
86e23f2aed | ||
|
|
a168107d29 | ||
|
|
63abd5b146 | ||
|
|
c55d15c0a9 | ||
|
|
91e364bde5 | ||
|
|
1723e989cd | ||
|
|
1a25aca45c | ||
|
|
cd61fc1e8c | ||
|
|
86a6a909d9 | ||
|
|
6cd698eeda | ||
|
|
edcee6d861 | ||
|
|
e6d5689bf9 | ||
|
|
efcf0e40f6 | ||
|
|
42e3ae92e3 | ||
|
|
b5464e2906 | ||
|
|
2c27083e2a | ||
|
|
2b6f84e5d8 | ||
|
|
c6b07561da | ||
|
|
1a47325055 | ||
|
|
dd1a7a1b3d | ||
|
|
79851101ae | ||
|
|
6808ee5a70 | ||
|
|
efeb65be4c | ||
|
|
10c699759e | ||
|
|
d69aa8d484 | ||
|
|
b95337fcb9 | ||
|
|
9921731fdf | ||
|
|
e7cccd099e | ||
|
|
be8cedfe7f | ||
|
|
1a548f32a1 | ||
|
|
72ff465300 | ||
|
|
716bb7118b | ||
|
|
4fe797eeab | ||
|
|
459a51c111 | ||
|
|
8c81e47564 | ||
|
|
f59046a924 | ||
|
|
e5c73d66d3 | ||
|
|
cf3512ee03 | ||
|
|
28277da35b | ||
|
|
f63b176328 | ||
|
|
ccb892feab | ||
|
|
5992b82180 | ||
|
|
6a33624416 | ||
|
|
06433a6ec6 | ||
|
|
e02e644d4c | ||
|
|
9942e86765 | ||
|
|
9f8862680c | ||
|
|
6e91005daa | ||
|
|
5031a8cb01 | ||
|
|
28810910b1 | ||
|
|
71badb959d | ||
|
|
bc94acb378 | ||
|
|
ca345d4755 | ||
|
|
b250890b1f | ||
|
|
18f22be2ba | ||
|
|
b02baa2cfb | ||
|
|
fb89eed8fa | ||
|
|
eb6557a6be | ||
|
|
4f114cbcab | ||
|
|
2cc24b675e | ||
|
|
40caa795fb | ||
|
|
e45a8a4591 | ||
|
|
3f1ed35f3e | ||
|
|
9deba3e715 | ||
|
|
1e2e907982 | ||
|
|
c3848e2e30 | ||
|
|
35d567e2e0 | ||
|
|
7099ec48e6 | ||
|
|
df8cf402c6 | ||
|
|
2b67fbcd98 | ||
|
|
ec9aad4dae | ||
|
|
99623f31af | ||
|
|
0e908ec71f |
2
.github/DockerfileBackendTests
vendored
2
.github/DockerfileBackendTests
vendored
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.78-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.79-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.4-slim-bookworm
|
||||
|
||||
FROM ${DEBIAN_IMAGE} as downloader
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 1.78.0
|
||||
toolchain: 1.79.0
|
||||
# - uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# workspaces: |
|
||||
|
||||
45
.github/workflows/build-caddy-l4-image.yml
vendored
Normal file
45
.github/workflows/build-caddy-l4-image.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/caddy-l4
|
||||
|
||||
name: Build caddy-l4
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build_ee:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: depot/setup-action@v1
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha
|
||||
type=ref,event=branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: ./docker
|
||||
file: ./docker/DockerfileCaddyL4
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
76
.github/workflows/docker-image-rpi4.yml
vendored
Normal file
76
.github/workflows/docker-image-rpi4.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}-rpi
|
||||
|
||||
name: Build windmill without jemalloc
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: windmill-without-jemalloc
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build:
|
||||
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: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=AGPLv3
|
||||
52
.github/workflows/docker-image.yml
vendored
52
.github/workflows/docker-image.yml
vendored
@@ -1,6 +1,7 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository || 'windmill-labs/windmill-test' }}
|
||||
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}', github.event.number) }}
|
||||
|
||||
name: Build windmill:main
|
||||
on:
|
||||
@@ -62,7 +63,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
@@ -113,7 +113,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -137,7 +136,7 @@ jobs:
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
@@ -176,7 +175,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -250,25 +248,25 @@ jobs:
|
||||
|
||||
- run: |
|
||||
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
- run: |
|
||||
# Checks the image is in docker prior to running actions-docker-extract. It fails if not
|
||||
# Also useful to visually check that the arch is the right opencontainers
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
path: "/usr/src/app/windmill"
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract-ee
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
path: "/usr/src/app/windmill"
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
@@ -294,25 +292,25 @@ jobs:
|
||||
|
||||
# - run: |
|
||||
# # pulling docker image with desired arch so that actions-docker-extract doesn't do it
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
# - run: |
|
||||
# # Checks the image is in docker prior to running actions-docker-extract. It fails if not
|
||||
# # Also useful to visually check that the arch is the right opencontainers
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# docker image inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
|
||||
# - uses: shrink/actions-docker-extract@v3
|
||||
# id: extract
|
||||
# with:
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
# path: "/usr/src/app/windmill"
|
||||
|
||||
# - uses: shrink/actions-docker-extract@v3
|
||||
# id: extract-ee
|
||||
# with:
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev
|
||||
# image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
# path: "/usr/src/app/windmill"
|
||||
|
||||
# - name: Rename binary with corresponding architecture
|
||||
@@ -354,6 +352,7 @@ jobs:
|
||||
tag_latest:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -366,12 +365,13 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Tag main and latest
|
||||
run: |
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
|
||||
|
||||
tag_latest_ee:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build_ee]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -384,8 +384,8 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Tag main and latest for ee
|
||||
run: |
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
docker buildx imagetools create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:main
|
||||
|
||||
verify_ee_image_vulnerabilities:
|
||||
runs-on: ubicloud
|
||||
@@ -432,6 +432,7 @@ jobs:
|
||||
build_ee_nsjail:
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -451,8 +452,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
|
||||
@@ -480,6 +479,7 @@ jobs:
|
||||
build_ee_reports_privately:
|
||||
needs: [build_ee_nsjail]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -503,8 +503,6 @@ jobs:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-reports
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
|
||||
@@ -589,7 +587,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
@@ -636,7 +633,6 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
|
||||
211
CHANGELOG.md
211
CHANGELOG.md
@@ -1,5 +1,216 @@
|
||||
# Changelog
|
||||
|
||||
## [1.377.1](https://github.com/windmill-labs/windmill/compare/v1.377.0...v1.377.1) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix timezone issues for Date only inputs ([#4215](https://github.com/windmill-labs/windmill/issues/4215)) ([2334802](https://github.com/windmill-labs/windmill/commit/2334802384e86c4269e1cbfd4f6f98a9a1e58f68))
|
||||
|
||||
## [1.377.0](https://github.com/windmill-labs/windmill/compare/v1.376.1...v1.377.0) (2024-08-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **app:** add user resource select component ([413ad2c](https://github.com/windmill-labs/windmill/commit/413ad2c9259fd30871b5184bc4f7c3fece60918d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Additional tracing when pulling and uploading search index [#4207](https://github.com/windmill-labs/windmill/issues/4207) ([4f9193c](https://github.com/windmill-labs/windmill/commit/4f9193c03664c336095e9aa2ea6232a4815c7387))
|
||||
* azure git sync test connection ([#4209](https://github.com/windmill-labs/windmill/issues/4209)) ([2c3d492](https://github.com/windmill-labs/windmill/commit/2c3d4920e0b575019dd18d0a4a9609e6f66be60e))
|
||||
* case insensitive encoding for email triggers ([#4211](https://github.com/windmill-labs/windmill/issues/4211)) ([55926f9](https://github.com/windmill-labs/windmill/commit/55926f957f83cf87a52070577ed79bed15e38836))
|
||||
* **cli:** Add esbuild loader for .node files for codebase ([a77f74c](https://github.com/windmill-labs/windmill/commit/a77f74cb7ea5ed3633564075169a5aeedae06152))
|
||||
* **frontend:** fix decision tree debug menu ([#4212](https://github.com/windmill-labs/windmill/issues/4212)) ([c90fb8d](https://github.com/windmill-labs/windmill/commit/c90fb8d293ef3aaa8264c2a2984e4203e4851f5c))
|
||||
* **frontend:** fix flow warnings ([#4213](https://github.com/windmill-labs/windmill/issues/4213)) ([bf629f3](https://github.com/windmill-labs/windmill/commit/bf629f39ccc411b6ed98081f414a8c3238d52957))
|
||||
* handle >1 num workers stats ([#4177](https://github.com/windmill-labs/windmill/issues/4177)) ([0029dc1](https://github.com/windmill-labs/windmill/commit/0029dc11e9f0828c4a129586ac90a18b3f4dfe79))
|
||||
* handle snowflake partitions ([#4214](https://github.com/windmill-labs/windmill/issues/4214)) ([d1e119e](https://github.com/windmill-labs/windmill/commit/d1e119e0526933cf39320eecf7c4836cd45d0eb0))
|
||||
|
||||
## [1.376.1](https://github.com/windmill-labs/windmill/compare/v1.376.0...v1.376.1) (2024-08-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* email triggers sqlx CE and improve email parsing ([#4203](https://github.com/windmill-labs/windmill/issues/4203)) ([6e9c350](https://github.com/windmill-labs/windmill/commit/6e9c350b4aa27355d03cb409da36e8f44ff548dc))
|
||||
* mysql params starting with underscore ([#4201](https://github.com/windmill-labs/windmill/issues/4201)) ([e7148f6](https://github.com/windmill-labs/windmill/commit/e7148f672303ccf2b5c49bd9d75ee9764657490e))
|
||||
|
||||
## [1.376.0](https://github.com/windmill-labs/windmill/compare/v1.375.0...v1.376.0) (2024-08-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* email triggers ([#4163](https://github.com/windmill-labs/windmill/issues/4163)) ([a87f34f](https://github.com/windmill-labs/windmill/commit/a87f34fb4a5e54da38e2dbabf2ac551d35b021ee))
|
||||
* secure ctx variables in runnable inputs ([#4142](https://github.com/windmill-labs/windmill/issues/4142)) ([b54edf1](https://github.com/windmill-labs/windmill/commit/b54edf153ec90b9bafad9ed7bc92c29345951fe6))
|
||||
* Tag filter on Runs page ([#4193](https://github.com/windmill-labs/windmill/issues/4193)) ([7b31281](https://github.com/windmill-labs/windmill/commit/7b3128171ea7193efea569297a099bb9ee6935e1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix native scripts access to reserved variables ([7886f8f](https://github.com/windmill-labs/windmill/commit/7886f8f471bcb33f7cc640eab64ffc0dd3cb1726))
|
||||
* **frontend:** disable email triggers by default ([#4199](https://github.com/windmill-labs/windmill/issues/4199)) ([5e3a3e2](https://github.com/windmill-labs/windmill/commit/5e3a3e2103ce6218a60c570546355ef6ca574df2))
|
||||
* **frontend:** fix the app created from a script or flow with the new topbar ([#4194](https://github.com/windmill-labs/windmill/issues/4194)) ([657f03b](https://github.com/windmill-labs/windmill/commit/657f03bc67f84a8593b28483684c2ad8ad432865))
|
||||
* **frontend:** Fr/improve suspend drawer ([#4189](https://github.com/windmill-labs/windmill/issues/4189)) ([5104dba](https://github.com/windmill-labs/windmill/commit/5104dba63140e2186dc1eed7f0bf4e14e357bf3d))
|
||||
* **frontend:** Hide AgChart background to make styling work ([#4197](https://github.com/windmill-labs/windmill/issues/4197)) ([b9b30e6](https://github.com/windmill-labs/windmill/commit/b9b30e66ec485cc019561b092fc27d08aa4666b3))
|
||||
* **frontend:** Remove full height for the event handlers of runnables ([#4196](https://github.com/windmill-labs/windmill/issues/4196)) ([6749f2c](https://github.com/windmill-labs/windmill/commit/6749f2c1367bdbecb12941baaa87b63c4b4c6f20))
|
||||
* mysql support for underscore in named param ([#4200](https://github.com/windmill-labs/windmill/issues/4200)) ([cfa20ae](https://github.com/windmill-labs/windmill/commit/cfa20ae0d740014baea2e534cea5f787acaeb028))
|
||||
|
||||
## [1.375.0](https://github.com/windmill-labs/windmill/compare/v1.374.0...v1.375.0) (2024-08-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* deployment UI filter deployable items ([#4183](https://github.com/windmill-labs/windmill/issues/4183)) ([f2f8bbe](https://github.com/windmill-labs/windmill/commit/f2f8bbe1d4364540854fef3bf11df5bb4252c17d))
|
||||
* improve indices of completed_runs for faster load ([cc111ba](https://github.com/windmill-labs/windmill/commit/cc111ba7dcd7fe6c4280d394d4eb0e723e9d8b19))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** add missing truncate for branch predicates ([#4180](https://github.com/windmill-labs/windmill/issues/4180)) ([947dd21](https://github.com/windmill-labs/windmill/commit/947dd219335244728213e95a46e577428ac65464))
|
||||
* **frontend:** fix style panel for compoentn without custom css ([#4182](https://github.com/windmill-labs/windmill/issues/4182)) ([43a89ee](https://github.com/windmill-labs/windmill/commit/43a89ee1ccbeeac11211277f8005d85541c71dc3))
|
||||
* **frontend:** fix tutorial for apps with the new topbar ([#4186](https://github.com/windmill-labs/windmill/issues/4186)) ([704e75e](https://github.com/windmill-labs/windmill/commit/704e75e893a94e71091c840aa5608837c0617526))
|
||||
|
||||
## [1.374.0](https://github.com/windmill-labs/windmill/compare/v1.373.1...v1.374.0) (2024-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for assets using tar for codebase deploy ([3508b6d](https://github.com/windmill-labs/windmill/commit/3508b6d793b0ce21c0c264a50bb000cd61c21920))
|
||||
* caddy with l4 image ([#4178](https://github.com/windmill-labs/windmill/issues/4178)) ([811de58](https://github.com/windmill-labs/windmill/commit/811de58712f619045a3653fe5b98ff0c7599f392))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix delete job ([86e23f2](https://github.com/windmill-labs/windmill/commit/86e23f2aed234a64fce46e1b6253f788546876af))
|
||||
|
||||
## [1.373.1](https://github.com/windmill-labs/windmill/compare/v1.373.0...v1.373.1) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix run_flow_async from call ([63abd5b](https://github.com/windmill-labs/windmill/commit/63abd5b14606869eafbd25d8469638b85b96e199))
|
||||
* **frontend:** Add support for array of objects in th UI ([#4170](https://github.com/windmill-labs/windmill/issues/4170)) ([91e364b](https://github.com/windmill-labs/windmill/commit/91e364bde5459447945d7dae65c1cab2858c69ac))
|
||||
|
||||
## [1.373.0](https://github.com/windmill-labs/windmill/compare/v1.372.0...v1.373.0) (2024-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Indexing improvements ([#4167](https://github.com/windmill-labs/windmill/issues/4167)) ([edcee6d](https://github.com/windmill-labs/windmill/commit/edcee6d8611ff96bfbca7ba811e5e67beaa80f5a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app forms default values changes gets propagated ([cd61fc1](https://github.com/windmill-labs/windmill/commit/cd61fc1e8c00cc3a57af0228cdf958b2fb3a2699))
|
||||
|
||||
## [1.372.0](https://github.com/windmill-labs/windmill/compare/v1.371.4...v1.372.0) (2024-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* variables created by password fields expire after 7 days ([b5464e2](https://github.com/windmill-labs/windmill/commit/b5464e2906bdaedb4752e198e40b7e8c4dbb1338))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix raw_deps handling ([efcf0e4](https://github.com/windmill-labs/windmill/commit/efcf0e40f68342f8330cf5d610979eb7fe92ce09))
|
||||
* improve cancel_selection job for running jobs ([42e3ae9](https://github.com/windmill-labs/windmill/commit/42e3ae92e3841579f8ac0821b58ebb72c4204c98))
|
||||
|
||||
## [1.371.4](https://github.com/windmill-labs/windmill/compare/v1.371.3...v1.371.4) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fix recompute all ([#4161](https://github.com/windmill-labs/windmill/issues/4161)) ([10c6997](https://github.com/windmill-labs/windmill/commit/10c699759e1d06d009783b9f5cba2cbf3ba6a7da))
|
||||
* **frontend:** If multiple recompute all present, interval is now in sync ([#4162](https://github.com/windmill-labs/windmill/issues/4162)) ([efeb65b](https://github.com/windmill-labs/windmill/commit/efeb65be4c91491bb4fbedfbc160da7517ebe1d6))
|
||||
* improve index usage and runs page performance ([d69aa8d](https://github.com/windmill-labs/windmill/commit/d69aa8d484de016ae7e7bbd190ce6c254818c861))
|
||||
|
||||
## [1.371.3](https://github.com/windmill-labs/windmill/compare/v1.371.2...v1.371.3) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* for codebase bundle, use cjs exports instead ([be8cedf](https://github.com/windmill-labs/windmill/commit/be8cedfe7fe42105a375c92536ec7f5e5a1343bf))
|
||||
|
||||
## [1.371.2](https://github.com/windmill-labs/windmill/compare/v1.371.1...v1.371.2) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve codebase handling by ignoring creating lock and bundle ([716bb71](https://github.com/windmill-labs/windmill/commit/716bb7118b0d5b8dd86799d5514f37b068613256))
|
||||
|
||||
## [1.371.1](https://github.com/windmill-labs/windmill/compare/v1.371.0...v1.371.1) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* database connections now scale linearly with number of subworkers ([f59046a](https://github.com/windmill-labs/windmill/commit/f59046a9249962ba7e1739de579cd8d885ec63a8))
|
||||
|
||||
## [1.371.0](https://github.com/windmill-labs/windmill/compare/v1.370.0...v1.371.0) (2024-07-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** support array of objects in schema ([#4106](https://github.com/windmill-labs/windmill/issues/4106)) ([5992b82](https://github.com/windmill-labs/windmill/commit/5992b821803d9cdc45fa0dee100f159dd3ed557f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* always consider electron as external for bundles ([06433a6](https://github.com/windmill-labs/windmill/commit/06433a6ec65534e1a11085cefd6a67699e4eb0a0))
|
||||
* fix bunnative lock creation ([6a33624](https://github.com/windmill-labs/windmill/commit/6a33624416cc48e2a1ab21afbd2ac4ab48be2559))
|
||||
* submit and form persistence on app on render change ([28277da](https://github.com/windmill-labs/windmill/commit/28277da35bae1c18f16e85d093989e0d419b3601))
|
||||
* support bunnative in the CLI ([e02e644](https://github.com/windmill-labs/windmill/commit/e02e644d4ca16773bae3cc2a6b52ac08417d3905))
|
||||
|
||||
## [1.370.0](https://github.com/windmill-labs/windmill/compare/v1.369.1...v1.370.0) (2024-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* get completed flow node result by api/download + eval list result json path optim ([#4108](https://github.com/windmill-labs/windmill/issues/4108)) ([5031a8c](https://github.com/windmill-labs/windmill/commit/5031a8cb015e3ba8d668f5de769b8d67da82c116))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve native runtime with axios support ([6e91005](https://github.com/windmill-labs/windmill/commit/6e91005daa4bbe2f0bd1f8b1c2cce1f68485f9ef))
|
||||
|
||||
## [1.369.1](https://github.com/windmill-labs/windmill/compare/v1.369.0...v1.369.1) (2024-07-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix lang picker artefact for script editor ([bc94acb](https://github.com/windmill-labs/windmill/commit/bc94acb378b1310271dd3dda80614e0e38b5285f))
|
||||
|
||||
## [1.369.0](https://github.com/windmill-labs/windmill/compare/v1.368.3...v1.369.0) (2024-07-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for text/plain webhook ([#4146](https://github.com/windmill-labs/windmill/issues/4146)) ([eb6557a](https://github.com/windmill-labs/windmill/commit/eb6557a6beb5d3b2324b30f2abc117b1a122c3f7))
|
||||
* **frontend:** App bar as components ([#4103](https://github.com/windmill-labs/windmill/issues/4103)) ([fb89eed](https://github.com/windmill-labs/windmill/commit/fb89eed8fa43568ffe3a22c5a6dd0b9f38890a37))
|
||||
* remove nativets in favor of bun with native pragma ([18f22be](https://github.com/windmill-labs/windmill/commit/18f22be2bae3136f1d9ff06c74f3751504bef60b))
|
||||
* remove nativets in favor of bun with native pragma ([b02baa2](https://github.com/windmill-labs/windmill/commit/b02baa2cfb242f7f6ed64e9861a8562272fb2974))
|
||||
|
||||
## [1.368.3](https://github.com/windmill-labs/windmill/compare/v1.368.2...v1.368.3) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update bun to 1.1.21 ([e45a8a4](https://github.com/windmill-labs/windmill/commit/e45a8a4591649de240a4bff0a25301a2586618bf))
|
||||
|
||||
## [1.368.2](https://github.com/windmill-labs/windmill/compare/v1.368.1...v1.368.2) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add the nobundling option for bun ([c3848e2](https://github.com/windmill-labs/windmill/commit/c3848e2e3011c7e74d5ae6eb85605476ef2998f4))
|
||||
* disable prebundling for nodejs mode scripts ([1e2e907](https://github.com/windmill-labs/windmill/commit/1e2e907982eab2ea7e713d9cfbec046527616fa2))
|
||||
|
||||
## [1.368.1](https://github.com/windmill-labs/windmill/compare/v1.368.0...v1.368.1) (2024-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve runs page performance through pg indices ([99623f3](https://github.com/windmill-labs/windmill/commit/99623f31af109463b065688058d0cab8ea9910de))
|
||||
|
||||
## [1.368.0](https://github.com/windmill-labs/windmill/compare/v1.367.2...v1.368.0) (2024-07-26)
|
||||
|
||||
|
||||
|
||||
10
Caddyfile
10
Caddyfile
@@ -1,3 +1,13 @@
|
||||
{
|
||||
layer4 {
|
||||
:25 {
|
||||
proxy {
|
||||
to windmill_server:2525
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://lsp:3001
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.78-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.79-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.8-slim-bookworm
|
||||
|
||||
FROM ${RUST_IMAGE} AS rust_base
|
||||
@@ -127,13 +127,13 @@ RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
targz='go1.21.6.linux-amd64.tar.gz'; \
|
||||
targz='go1.22.5.linux-amd64.tar.gz'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
targz='go1.21.6.linux-arm64.tar.gz'; \
|
||||
targz='go1.22.5.linux-arm64.tar.gz'; \
|
||||
;; \
|
||||
'armhf') \
|
||||
targz='go1.21.6.linux-armv6l.tar.gz'; \
|
||||
targz='go1.22.5.linux-armv6l.tar.gz'; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
|
||||
esac; \
|
||||
@@ -155,9 +155,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.44.4 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
COPY --from=denoland/deno:1.45.4 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.1.18 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.1.21 /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
|
||||
@@ -173,4 +173,4 @@ RUN windmill cache
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["windmill"]
|
||||
CMD ["windmill"]
|
||||
|
||||
8
app-viewer-bundle/postcss.config.js
Normal file
8
app-viewer-bundle/postcss.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"tailwindcss/nesting": "postcss-nesting",
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
950
app-viewer-bundle/tailwind.config.js
Normal file
950
app-viewer-bundle/tailwind.config.js
Normal file
@@ -0,0 +1,950 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
|
||||
const lightTheme = {
|
||||
surface: "#ffffff",
|
||||
surfaceSecondary: "#f3f4f6",
|
||||
surfaceHover: "#e5e7eb",
|
||||
surfaceDisabled: "#f9fafb",
|
||||
surfaceSelected: "#d1d5db",
|
||||
|
||||
textPrimary: "#2d3748",
|
||||
textSecondary: "#4a5568",
|
||||
textTertiary: "#505c70",
|
||||
textDisabled: "#a0aec0",
|
||||
|
||||
border: "#ddd",
|
||||
borderHover: "#ccc",
|
||||
};
|
||||
|
||||
const lightThemeRgb = makeRgb(lightTheme);
|
||||
|
||||
const darkTheme = {
|
||||
surface: "#2e3440",
|
||||
surfaceSecondary: "#3b4252",
|
||||
surfaceHover: "#454F64",
|
||||
surfaceDisabled: "#212732",
|
||||
surfaceSelected: "#434c5e",
|
||||
|
||||
textPrimary: "#EEEEEE",
|
||||
textSecondary: "#C2C9D1",
|
||||
textTertiary: "#A8AEB7",
|
||||
textDisabled: "#989DA5",
|
||||
|
||||
border: "#3e4c60",
|
||||
borderHover: "#3e4c60",
|
||||
};
|
||||
|
||||
const darkThemeRgb = makeRgb(darkTheme);
|
||||
|
||||
function makeRgb(theme) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(theme).map(([key, value]) => {
|
||||
if (typeof value === "string" && value.startsWith("#")) {
|
||||
return [key, hexToRgb(value)];
|
||||
}
|
||||
|
||||
return [key, value];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function hexToRgb(hex) {
|
||||
// Remove '#' symbol from the beginning of the hex value
|
||||
hex = hex.replace("#", "");
|
||||
|
||||
// Convert the hex value to decimal
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
// Return the RGB string format
|
||||
return `${r} ${g} ${b}`;
|
||||
}
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
content: [
|
||||
"./src/**/*.{html,js,svelte,ts}",
|
||||
"./node_modules/windmill-components/**/*.{html,js,svelte,ts}",
|
||||
],
|
||||
safelist: [
|
||||
"hljs",
|
||||
"splitpanes__pane",
|
||||
"splitpanes__splitter",
|
||||
"wm-tab",
|
||||
"autocomplete-list",
|
||||
"autocomplete-list-item",
|
||||
"autocomplete-list-item-create",
|
||||
"selected",
|
||||
"wm-tab-selected",
|
||||
...(process.env.NODE_ENV === "production"
|
||||
? [
|
||||
{ pattern: /^m(\w?)-.*$/ },
|
||||
{ pattern: /^p(\w?)-.*$/ },
|
||||
{ pattern: /^rounded-.*$/ },
|
||||
{ pattern: /^shadow-.*$/, variants: ["hover"] },
|
||||
{ pattern: /^text-[^/]*$/, variants: ["hover", "active", "focus"] },
|
||||
{ pattern: /^bg-[^/]*$/, variants: ["hover", "active", "focus"] },
|
||||
{ pattern: /^border-[^/]*$/, variants: ["hover", "active", "focus"] },
|
||||
{ pattern: /^ring-[^/]*$/, variants: ["hover", "active", "focus"] },
|
||||
]
|
||||
: []),
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
current: "currentcolor",
|
||||
transparent: "transparent",
|
||||
white: "#ffffff",
|
||||
black: "#000000",
|
||||
slate: {
|
||||
50: "#f8fafc",
|
||||
100: "#f1f5f9",
|
||||
200: "#e2e8f0",
|
||||
300: "#cbd5e1",
|
||||
400: "#94a3b8",
|
||||
500: "#64748b",
|
||||
600: "#475569",
|
||||
700: "#334155",
|
||||
800: "#1e293b",
|
||||
900: "#0f172a",
|
||||
},
|
||||
zinc: {
|
||||
50: "#fafafa",
|
||||
100: "#f4f4f5",
|
||||
200: "#e4e4e7",
|
||||
300: "#d4d4d8",
|
||||
400: "#a1a1aa",
|
||||
500: "#71717a",
|
||||
600: "#52525b",
|
||||
700: "#3f3f46",
|
||||
800: "#27272a",
|
||||
900: "#18181b",
|
||||
},
|
||||
neutral: {
|
||||
50: "#fafafa",
|
||||
100: "#f5f5f5",
|
||||
200: "#e5e5e5",
|
||||
300: "#d4d4d4",
|
||||
400: "#a3a3a3",
|
||||
500: "#737373",
|
||||
600: "#525252",
|
||||
700: "#404040",
|
||||
800: "#262626",
|
||||
900: "#171717",
|
||||
},
|
||||
stone: {
|
||||
50: "#fafaf9",
|
||||
100: "#f5f5f4",
|
||||
200: "#e7e5e4",
|
||||
300: "#d6d3d1",
|
||||
400: "#a8a29e",
|
||||
500: "#78716c",
|
||||
600: "#57534e",
|
||||
700: "#44403c",
|
||||
800: "#292524",
|
||||
900: "#1c1917",
|
||||
},
|
||||
orange: {
|
||||
50: "#fff7ed",
|
||||
100: "#ffedd5",
|
||||
200: "#fed7aa",
|
||||
300: "#fdba74",
|
||||
400: "#fb923c",
|
||||
500: "#f97316",
|
||||
600: "#ea580c",
|
||||
700: "#c2410c",
|
||||
800: "#9a3412",
|
||||
900: "#7c2d12",
|
||||
},
|
||||
amber: {
|
||||
50: "#fffbeb",
|
||||
100: "#fef3c7",
|
||||
200: "#fde68a",
|
||||
300: "#fcd34d",
|
||||
400: "#fbbf24",
|
||||
500: "#f59e0b",
|
||||
600: "#d97706",
|
||||
700: "#b45309",
|
||||
800: "#92400e",
|
||||
900: "#78350f",
|
||||
},
|
||||
lime: {
|
||||
50: "#f7fee7",
|
||||
100: "#ecfccb",
|
||||
200: "#d9f99d",
|
||||
300: "#bef264",
|
||||
400: "#a3e635",
|
||||
500: "#84cc16",
|
||||
600: "#65a30d",
|
||||
700: "#4d7c0f",
|
||||
800: "#3f6212",
|
||||
900: "#365314",
|
||||
},
|
||||
emerald: {
|
||||
50: "#ecfdf5",
|
||||
100: "#d1fae5",
|
||||
200: "#a7f3d0",
|
||||
300: "#6ee7b7",
|
||||
400: "#34d399",
|
||||
500: "#10b981",
|
||||
600: "#059669",
|
||||
700: "#047857",
|
||||
800: "#065f46",
|
||||
900: "#064e3b",
|
||||
},
|
||||
teal: {
|
||||
50: "#f0fdfa",
|
||||
100: "#ccfbf1",
|
||||
200: "#99f6e4",
|
||||
300: "#5eead4",
|
||||
400: "#2dd4bf",
|
||||
500: "#14b8a6",
|
||||
600: "#0d9488",
|
||||
700: "#0f766e",
|
||||
800: "#115e59",
|
||||
900: "#134e4a",
|
||||
},
|
||||
cyan: {
|
||||
50: "#ecfeff",
|
||||
100: "#cffafe",
|
||||
200: "#a5f3fc",
|
||||
300: "#67e8f9",
|
||||
400: "#22d3ee",
|
||||
500: "#06b6d4",
|
||||
600: "#0891b2",
|
||||
700: "#0e7490",
|
||||
800: "#155e75",
|
||||
900: "#164e63",
|
||||
},
|
||||
sky: {
|
||||
50: "#f0f9ff",
|
||||
100: "#e0f2fe",
|
||||
200: "#bae6fd",
|
||||
300: "#7dd3fc",
|
||||
400: "#38bdf8",
|
||||
500: "#0ea5e9",
|
||||
600: "#0284c7",
|
||||
700: "#0369a1",
|
||||
800: "#075985",
|
||||
900: "#0c4a6e",
|
||||
},
|
||||
violet: {
|
||||
50: "#f5f3ff",
|
||||
100: "#ede9fe",
|
||||
200: "#ddd6fe",
|
||||
300: "#c4b5fd",
|
||||
400: "#a78bfa",
|
||||
500: "#8b5cf6",
|
||||
600: "#7c3aed",
|
||||
700: "#6d28d9",
|
||||
800: "#5b21b6",
|
||||
900: "#4c1d95",
|
||||
},
|
||||
purple: {
|
||||
50: "#faf5ff",
|
||||
100: "#f3e8ff",
|
||||
200: "#e9d5ff",
|
||||
300: "#d8b4fe",
|
||||
400: "#c084fc",
|
||||
500: "#a855f7",
|
||||
600: "#9333ea",
|
||||
700: "#7e22ce",
|
||||
800: "#6b21a8",
|
||||
900: "#581c87",
|
||||
},
|
||||
fuchsia: {
|
||||
50: "#fdf4ff",
|
||||
100: "#fae8ff",
|
||||
200: "#f5d0fe",
|
||||
300: "#f0abfc",
|
||||
400: "#e879f9",
|
||||
500: "#d946ef",
|
||||
600: "#c026d3",
|
||||
700: "#a21caf",
|
||||
800: "#86198f",
|
||||
900: "#701a75",
|
||||
},
|
||||
pink: {
|
||||
50: "#fdf2f8",
|
||||
100: "#fce7f3",
|
||||
200: "#fbcfe8",
|
||||
300: "#f9a8d4",
|
||||
400: "#f472b6",
|
||||
500: "#ec4899",
|
||||
600: "#db2777",
|
||||
700: "#be185d",
|
||||
800: "#9d174d",
|
||||
900: "#831843",
|
||||
},
|
||||
rose: {
|
||||
50: "#fff1f2",
|
||||
100: "#ffe4e6",
|
||||
200: "#fecdd3",
|
||||
300: "#fda4af",
|
||||
400: "#fb7185",
|
||||
500: "#f43f5e",
|
||||
600: "#e11d48",
|
||||
700: "#be123c",
|
||||
800: "#9f1239",
|
||||
900: "#881337",
|
||||
},
|
||||
gray: {
|
||||
50: "#f9fafb",
|
||||
100: "#f3f4f6",
|
||||
200: "#e5e7eb",
|
||||
300: "#d1d5db",
|
||||
400: "#9ca3af",
|
||||
500: "#6b7280",
|
||||
600: "#4b5563",
|
||||
700: "#374151",
|
||||
800: "#1f2937",
|
||||
900: "#111827",
|
||||
},
|
||||
red: {
|
||||
50: "#fef2f2",
|
||||
100: "#fee2e2",
|
||||
200: "#fecaca",
|
||||
300: "#fca5a5",
|
||||
400: "#f87171",
|
||||
500: "#ef4444",
|
||||
600: "#dc2626",
|
||||
700: "#b91c1c",
|
||||
800: "#991b1b",
|
||||
900: "#7f1d1d",
|
||||
},
|
||||
orange: {
|
||||
100: "#ffedd5",
|
||||
200: "#ffedd5",
|
||||
300: "#FDC089",
|
||||
400: "#fb923c",
|
||||
500: "#f97316",
|
||||
600: "#ea580c",
|
||||
700: "#c2410c",
|
||||
800: "#c2410c",
|
||||
},
|
||||
yellow: {
|
||||
50: "#fefce8",
|
||||
100: "#fef9c3",
|
||||
200: "#fef08a",
|
||||
300: "#fde047",
|
||||
400: "#facc15",
|
||||
500: "#eab308",
|
||||
600: "#ca8a04",
|
||||
700: "#a16207",
|
||||
800: "#854d0e",
|
||||
900: "#713f12",
|
||||
},
|
||||
green: {
|
||||
50: "#f0fdf4",
|
||||
100: "#dcfce7",
|
||||
200: "#bbf7d0",
|
||||
300: "#86efac",
|
||||
400: "#4ade80",
|
||||
500: "#22c55e",
|
||||
600: "#16a34a",
|
||||
700: "#15803d",
|
||||
800: "#166534",
|
||||
900: "#14532d",
|
||||
},
|
||||
blue: {
|
||||
50: "#eff6ff",
|
||||
100: "#dbeafe",
|
||||
200: "#bfdbfe",
|
||||
300: "#93c5fd",
|
||||
400: "#60a5fa",
|
||||
500: "#3b82f6",
|
||||
600: "#2563eb",
|
||||
700: "#1d4ed8",
|
||||
800: "#1e40af",
|
||||
900: "#1e3a8a",
|
||||
},
|
||||
|
||||
indigo: {
|
||||
50: "#eef2ff",
|
||||
100: "#e0e7ff",
|
||||
200: "#c7d2fe",
|
||||
300: "#a5b4fc",
|
||||
400: "#818cf8",
|
||||
500: "#6366f1",
|
||||
600: "#4f46e5",
|
||||
700: "#4338ca",
|
||||
800: "#3730a3",
|
||||
900: "#312e81",
|
||||
},
|
||||
frost: {
|
||||
50: "#f5f7fa",
|
||||
100: "#eaeef4",
|
||||
200: "#cfd9e8",
|
||||
300: "#a6bad3",
|
||||
400: "#7594bb",
|
||||
500: "#5e81ac",
|
||||
600: "#415f88",
|
||||
700: "#364d6e",
|
||||
800: "#2f425d",
|
||||
900: "#2b394f",
|
||||
950: "#1d2534",
|
||||
},
|
||||
marine: {
|
||||
50: "#E9E9F4",
|
||||
100: "#859AC7",
|
||||
200: "#586F9E",
|
||||
300: "#4A5F8A",
|
||||
400: "#394A6D",
|
||||
500: "#323F5B",
|
||||
},
|
||||
surface: "rgb(var(--color-surface) / <alpha-value>)",
|
||||
"surface-secondary":
|
||||
"rgb(var(--color-surface-secondary) / <alpha-value>)",
|
||||
"surface-hover": "rgb(var(--color-surface-hover) / <alpha-value>)",
|
||||
"surface-disabled": "rgb(var(--color-surface-disabled) / <alpha-value>)",
|
||||
"surface-selected": "rgb(var(--color-surface-selected) / <alpha-value>)",
|
||||
|
||||
primary: "rgb(var(--color-text-primary) / <alpha-value>)",
|
||||
secondary: "rgb(var(--color-text-secondary) / <alpha-value>)",
|
||||
tertiary: "rgb(var(--color-text-tertiary) / <alpha-value>)",
|
||||
disabled: "rgb(var(--color-text-disabled) / <alpha-value>)",
|
||||
|
||||
"surface-inverse": "rgb(var(--color-surface-inverse) / <alpha-value>)",
|
||||
"surface-secondary-inverse":
|
||||
"rgb(var(--color-surface-secondary-inverse) / <alpha-value>)",
|
||||
"surface-hover-inverse":
|
||||
"rgb(var(--color-surface-hover-inverse) / <alpha-value>)",
|
||||
"surface-disabled-inverse":
|
||||
"rgb(var(--color-surface-disabled-inverse) / <alpha-value>)",
|
||||
"surface-selected-inverse":
|
||||
"rgb(var(--color-surface-selected-inverse) / <alpha-value>)",
|
||||
|
||||
"primary-inverse":
|
||||
"rgb(var(--color-text-primary-inverse) / <alpha-value>)",
|
||||
"secondary-inverse":
|
||||
"rgb(var(--color-text-secondary-inverse) / <alpha-value>)",
|
||||
"tertiary-inverse":
|
||||
"rgb(var(--color-text-tertiary-inverse) / <alpha-value>)",
|
||||
"disabled-inverse":
|
||||
"rgb(var(--color-text-disabled-inverse) / <alpha-value>)",
|
||||
},
|
||||
fontFamily: {
|
||||
// add double quotes if there is space in font name
|
||||
main: ["Inter", "sans-serif"],
|
||||
mono: [
|
||||
"ui-monospace",
|
||||
"SFMono-Regular",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Consolas",
|
||||
'"Liberation Mono"',
|
||||
'"Courier New"',
|
||||
"monospace",
|
||||
],
|
||||
},
|
||||
extend: {
|
||||
border: {
|
||||
color: "red",
|
||||
},
|
||||
borderRadius: {
|
||||
component: "0.250rem",
|
||||
},
|
||||
maxHeight: {
|
||||
"1/2": "50vh",
|
||||
"2/3": "66vh",
|
||||
"3/4": "75vh",
|
||||
},
|
||||
minWidth: {
|
||||
"1/4": "25%",
|
||||
"1/3": "33%",
|
||||
"1/2": "50%",
|
||||
"2/3": "66%",
|
||||
"3/4": "75%",
|
||||
},
|
||||
minHeight: {
|
||||
"1/2": "50vh",
|
||||
},
|
||||
height: {
|
||||
"2/3": "66vh",
|
||||
},
|
||||
transitionProperty: {
|
||||
height: "height",
|
||||
},
|
||||
fontSize: {
|
||||
"2xs": "0.7rem",
|
||||
},
|
||||
screens: {
|
||||
fhd: "1900px",
|
||||
qhd: "2500px",
|
||||
"4k": "3800px",
|
||||
},
|
||||
animation: {
|
||||
"spin-counter-clockwise": "spin-counter-clockwise 1s linear infinite",
|
||||
},
|
||||
keyframes: {
|
||||
"spin-counter-clockwise": {
|
||||
to: { transform: "rotate(-360deg)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
require("@tailwindcss/typography"),
|
||||
// scopedPreflightStyles({
|
||||
// isolationStrategy: isolateInsideOfContainer("#windmill-app"),
|
||||
// }),
|
||||
plugin(({ addBase, addComponents, addUtilities, theme }) => {
|
||||
addBase({
|
||||
fontFamily: theme("fontFamily.main"),
|
||||
fontSize: theme("fontSize.base"),
|
||||
fontWeight: theme("fontWeight.normal"),
|
||||
|
||||
backgroundColor: "rgb(var(--color-surface) / <alpha-value>)",
|
||||
color: lightTheme.textPrimary,
|
||||
"--color-surface": lightThemeRgb.surface,
|
||||
"--color-surface-secondary": lightThemeRgb.surfaceSecondary,
|
||||
"--color-surface-hover": lightThemeRgb.surfaceHover,
|
||||
"--color-surface-disabled": lightThemeRgb.surfaceDisabled,
|
||||
"--color-surface-selected": lightThemeRgb.surfaceSelected,
|
||||
|
||||
"--color-text-primary": lightThemeRgb.textPrimary,
|
||||
"--color-text-secondary": lightThemeRgb.textSecondary,
|
||||
"--color-text-tertiary": lightThemeRgb.textTertiary,
|
||||
"--color-text-disabled": lightThemeRgb.textDisabled,
|
||||
|
||||
"--color-surface-inverse": darkThemeRgb.surface,
|
||||
"--color-surface-secondary-inverse": darkThemeRgb.surfaceSecondary,
|
||||
"--color-surface-hover-inverse": darkThemeRgb.surfaceHover,
|
||||
"--color-surface-disabled-inverse": darkThemeRgb.surfaceDisabled,
|
||||
"--color-surface-selected-inverse": darkThemeRgb.surfaceSelected,
|
||||
|
||||
"--color-text-primary-inverse": darkThemeRgb.textPrimary,
|
||||
"--color-text-secondary-inverse": darkThemeRgb.textSecondary,
|
||||
"--color-text-tertiary-inverse": darkThemeRgb.textTertiary,
|
||||
"--color-text-disabled-inverse": darkThemeRgb.textDisabled,
|
||||
|
||||
"--color-border": lightThemeRgb.border,
|
||||
"--color-border-hover": lightThemeRgb.borderHover,
|
||||
|
||||
"--vscode-editorSuggestWidget-background": "#f3f3f3",
|
||||
"--vscode-editorHoverWidget-foreground": "#616161",
|
||||
"--vscode-editorHoverWidget-border": "#c8c8c8",
|
||||
"--vscode-editorHoverWidget-statusBarBackground": "#e7e7e7",
|
||||
"--vscode-editorSuggestWidget-foreground": "#eeffff",
|
||||
"--vscode-editorSuggestWidget-highlightForeground": "#80cbc4",
|
||||
"--vscode-editorSuggestWidget-selectedBackground":
|
||||
"rgba(0, 0, 0, 0.31)",
|
||||
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: theme("fontSize.lg"),
|
||||
},
|
||||
|
||||
"&.dark": {
|
||||
backgroundColor: darkTheme.surface,
|
||||
color: darkTheme.textPrimary,
|
||||
|
||||
"--color-surface": darkThemeRgb.surface,
|
||||
"--color-surface-secondary": darkThemeRgb.surfaceSecondary,
|
||||
"--color-surface-hover": darkThemeRgb.surfaceHover,
|
||||
"--color-surface-disabled": darkThemeRgb.surfaceDisabled,
|
||||
"--color-surface-selected": darkThemeRgb.surfaceSelected,
|
||||
|
||||
"--color-text-primary": darkThemeRgb.textPrimary,
|
||||
"--color-text-secondary": darkThemeRgb.textSecondary,
|
||||
"--color-text-tertiary": darkThemeRgb.textTertiary,
|
||||
"--color-text-disabled": darkThemeRgb.textDisabled,
|
||||
|
||||
"--color-surface-inverse": lightThemeRgb.surface,
|
||||
"--color-surface-secondary-inverse": lightThemeRgb.surfaceSecondary,
|
||||
"--color-surface-hover-inverse": lightThemeRgb.surfaceHover,
|
||||
"--color-surface-disabled-inverse": lightThemeRgb.surfaceDisabled,
|
||||
"--color-surface-selected-inverse": lightThemeRgb.surfaceSelected,
|
||||
|
||||
"--color-text-primary-inverse": lightThemeRgb.textPrimary,
|
||||
"--color-text-secondary-inverse": lightThemeRgb.textSecondary,
|
||||
"--color-text-tertiary-inverse": lightThemeRgb.textTertiary,
|
||||
"--color-text-disabled-inverse": lightThemeRgb.textDisabled,
|
||||
|
||||
"--color-border": darkThemeRgb.border,
|
||||
"--color-border-hover": darkThemeRgb.borderHover,
|
||||
|
||||
"--vscode-editorSuggestWidget-background": "#252526",
|
||||
"--vscode-editorHoverWidget-foreground": "#cccccc",
|
||||
"--vscode-editorHoverWidget-border": "#454545",
|
||||
"--vscode-editorHoverWidget-statusBarBackground": "#2c2c2d",
|
||||
},
|
||||
|
||||
h1: {
|
||||
fontSize: "24px",
|
||||
fontWeight: theme("fontWeight.extrabold"),
|
||||
lineHeight: "1.05",
|
||||
[`@media (min-width: ${theme("screens.lg")})`]: {
|
||||
fontSize: "26px",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "29px",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "34px",
|
||||
},
|
||||
},
|
||||
h2: {
|
||||
fontSize: "20px",
|
||||
fontWeight: theme("fontWeight.extrabold"),
|
||||
lineHeight: "1.1",
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "22px",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "25px",
|
||||
},
|
||||
},
|
||||
mark: {
|
||||
backgroundColor: theme("colors.yellow.200"),
|
||||
borderRadius: theme("borderRadius.sm"),
|
||||
},
|
||||
h3: {
|
||||
fontSize: "18px",
|
||||
fontWeight: theme("fontWeight.bold"),
|
||||
lineHeight: "1.2",
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "20px",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "22px",
|
||||
},
|
||||
},
|
||||
h4: {
|
||||
fontSize: "18px",
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
lineHeight: "1.3",
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "20px",
|
||||
},
|
||||
},
|
||||
h5: {
|
||||
fontSize: "16px",
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
lineHeight: "1.5",
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "18px",
|
||||
},
|
||||
},
|
||||
h6: {
|
||||
fontSize: "16px",
|
||||
fontWeight: theme("fontWeight.medium"),
|
||||
lineHeight: "1.5",
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "18px",
|
||||
},
|
||||
},
|
||||
button: {
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
},
|
||||
a: {
|
||||
color: theme("colors.blue.500"),
|
||||
},
|
||||
".dark input::placeholder": {
|
||||
color: theme("colors.gray.400"),
|
||||
},
|
||||
"[type='checkbox']:checked": {
|
||||
backgroundImage: `url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");`,
|
||||
},
|
||||
".dark [type='checkbox']:checked": {
|
||||
backgroundImage: `url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");`,
|
||||
},
|
||||
'input:not(.windmillapp),input[type="text"]:not(.windmillapp),input[type="email"]:not(.windmillapp),input[type="url"]:not(.windmillapp),input[type="password"]:not(.windmillapp),input[type="number"]:not(.windmillapp),input[type="date"]:not(.windmillapp),input[type="datetime-local"]:not(.windmillapp),input[type="month"]:not(.windmillapp),input[type="search"]:not(.windmillapp),input[type="tel"]:not(.windmillapp),input[type="time"]:not(.windmillapp),input[type="week"]:not(.windmillapp),textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),select:not(.windmillapp)':
|
||||
{
|
||||
display: "block",
|
||||
fontSize: theme("fontSize.sm"),
|
||||
boxShadow: theme("boxShadow.sm"),
|
||||
width: "100%",
|
||||
padding: `${theme("spacing.1")} ${theme("spacing.2")}`,
|
||||
border: `1px solid ${theme("colors.gray.300")}`,
|
||||
borderRadius: theme("borderRadius.md"),
|
||||
"&:focus": {
|
||||
"--tw-ring-color": theme("colors.frost.100"),
|
||||
"--tw-ring-offset-shadow":
|
||||
"var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)",
|
||||
"--tw-ring-shadow":
|
||||
"var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)",
|
||||
boxShadow:
|
||||
"var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)",
|
||||
},
|
||||
"&:disabled,[disabled]": {
|
||||
backgroundColor: theme("colors.gray.100") + " !important",
|
||||
".dark &": {
|
||||
backgroundColor: theme("colors.gray.700") + " !important",
|
||||
},
|
||||
},
|
||||
},
|
||||
'.dark input:not(.windmillapp),.dark input[type="text"]:not(.windmillapp),.dark input[type="email"]:not(.windmillapp),.dark input[type="url"]:not(.windmillapp),.dark input[type="password"]:not(.windmillapp),.dark input[type="number"]:not(.windmillapp),.dark input[type="date"]:not(.windmillapp),.dark input[type="datetime-local"]:not(.windmillapp),.dark input[type="month"]:not(.windmillapp),.dark input[type="search"]:not(.windmillapp),.dark input[type="tel"]:not(.windmillapp),.dark input[type="time"]:not(.windmillapp),.dark input[type="week"]:not(.windmillapp),.dark textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),.dark select:not(.windmillapp)':
|
||||
{
|
||||
backgroundColor: theme("colors.gray.700"),
|
||||
color: theme("colors.gray.200"),
|
||||
borderColor: theme("colors.gray.600"),
|
||||
"&:focus": {
|
||||
"--tw-ring-color": theme("colors.frost.700"),
|
||||
},
|
||||
},
|
||||
|
||||
"button:disabled,button[disabled=true],a:disabled,a[disabled=true]": {
|
||||
pointerEvents: "none",
|
||||
cursor: "default",
|
||||
opacity: "0.90",
|
||||
},
|
||||
"pre code.hljs": {
|
||||
padding: "0px !important",
|
||||
fontFamily: theme("fontFamily.mono"),
|
||||
fontSize: theme("fontSize.xs") + " !important",
|
||||
lineHeight: theme("lineHeight.4") + " !important",
|
||||
},
|
||||
".h1-textarea": {
|
||||
fontSize: "24px !important",
|
||||
fontWeight: `${theme("fontWeight.extrabold")} !important`,
|
||||
lineHeight: "1.05 !important",
|
||||
color: theme("!colors.gray.800"),
|
||||
[`@media (min-width: ${theme("screens.lg")})`]: {
|
||||
fontSize: "26px !important",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "29px !important",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "34px !important",
|
||||
},
|
||||
},
|
||||
".h3-textarea": {
|
||||
fontSize: "18px !important",
|
||||
fontWeight: `${theme("fontWeight.bold")} !important`,
|
||||
lineHeight: "1.2 !important",
|
||||
color: theme("!colors.gray.600"),
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "20px !important",
|
||||
},
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "22px !important",
|
||||
},
|
||||
},
|
||||
".p-textarea": {
|
||||
fontSize: "16px !important",
|
||||
fontWeight: `${theme("fontWeight.normal")} !important`,
|
||||
lineHeight: "1.5 !important",
|
||||
color: theme("!colors.gray.600"),
|
||||
[`@media (min-width: ${theme("screens.fhd")})`]: {
|
||||
fontSize: "18px !important",
|
||||
},
|
||||
|
||||
[`@media (min-width: ${theme("screens.qhd")})`]: {
|
||||
fontSize: "20px !important",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
backgroundColor: theme("colors.surface-secondary") + " !important",
|
||||
},
|
||||
textarea: {
|
||||
backgroundColor: theme("colors.surface-secondary") + " !important",
|
||||
},
|
||||
});
|
||||
addComponents({
|
||||
".table-custom": {
|
||||
"& th": {
|
||||
paddingTop: theme("spacing.3"),
|
||||
paddingRight: theme("spacing.1"),
|
||||
paddingLeft: theme("spacing.1"),
|
||||
paddingBottom: theme("spacing.3"),
|
||||
fontSize: theme("fontSize.sm"),
|
||||
textAlign: "left",
|
||||
fontWeight: theme("fontWeight.semibold"),
|
||||
color: theme("colors.gray.900"),
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
".dark & th": {
|
||||
color: theme("colors.gray.200"),
|
||||
},
|
||||
"& td": {
|
||||
paddingLeft: theme("spacing.1"),
|
||||
paddingRight: theme("spacing.1"),
|
||||
paddingTop: theme("spacing.2"),
|
||||
paddingBottom: theme("spacing.2"),
|
||||
fontSize: theme("fontSize.sm"),
|
||||
color: theme("colors.gray.700"),
|
||||
},
|
||||
".dark & td ": {
|
||||
color: theme("colors.gray.200"),
|
||||
},
|
||||
"& tbody > :not([hidden]) ~ :not([hidden])": {
|
||||
borderTop: `1px solid ${theme("colors.gray.200")}`,
|
||||
},
|
||||
".dark & tbody > :not([hidden]) ~ :not([hidden])": {
|
||||
borderTop: `1px solid ${theme("colors.gray.700")}`,
|
||||
},
|
||||
"& tbody > tr:hover": {
|
||||
backgroundColor: theme("colors.gray.50"),
|
||||
},
|
||||
".dark & tbody > tr:hover": {
|
||||
backgroundColor: theme("colors.gray.800"),
|
||||
},
|
||||
},
|
||||
".commit-hash": {
|
||||
fontSize: theme("fontSize.2xs"),
|
||||
color: theme("colors.gray.500"),
|
||||
backgroundColor: theme("colors.gray.200"),
|
||||
fontFamily: theme("fontFamily.mono"),
|
||||
},
|
||||
".input-error": {
|
||||
borderColor: `${theme("colors.red.500")} !important`,
|
||||
},
|
||||
".box": {
|
||||
borderWidth: "1px",
|
||||
borderRadius: theme("borderRadius.sm"),
|
||||
boxShadow: theme("boxShadow.sm"),
|
||||
padding: theme("spacing.4"),
|
||||
},
|
||||
".animate-skeleton": {
|
||||
animation: theme("animation.pulse"),
|
||||
backgroundColor: theme("colors.blue.100"),
|
||||
borderRadius: theme("borderRadius.DEFAULT"),
|
||||
},
|
||||
".text-black-gradient": {
|
||||
color: "transparent",
|
||||
backgroundClip: "text",
|
||||
backgroundImage: `linear-gradient(to right, ${theme(
|
||||
"colors.black"
|
||||
)}, ${theme("colors.gray.600")})`,
|
||||
},
|
||||
".splitpanes__pane": {
|
||||
backgroundColor: lightTheme.surface + " !important",
|
||||
overflow: "auto !important",
|
||||
},
|
||||
".dark .splitpanes__pane": {
|
||||
backgroundColor: darkTheme.surface + " !important",
|
||||
overflow: "auto !important",
|
||||
},
|
||||
".splitpanes__splitter": {
|
||||
backgroundColor: lightTheme.border + " !important",
|
||||
margin: "0 !important",
|
||||
border: "none !important",
|
||||
"&::after": {
|
||||
backgroundColor: lightTheme.border + " !important",
|
||||
margin: "0 !important",
|
||||
transform: "none !important",
|
||||
transition: "opacity 200ms !important",
|
||||
opacity: "0",
|
||||
"--splitter-hover-size": "5px",
|
||||
"--splitter-hover-adjustment": "-2px",
|
||||
},
|
||||
"&:hover::after": {
|
||||
opacity: "1",
|
||||
zIndex: "1001 !important",
|
||||
},
|
||||
},
|
||||
".dark .splitpanes__splitter": {
|
||||
backgroundColor: darkTheme.border + " !important",
|
||||
"&::after": {
|
||||
backgroundColor: darkTheme.border + " !important",
|
||||
},
|
||||
},
|
||||
".splitpanes--vertical>.splitpanes__splitter": {
|
||||
width: "1px !important",
|
||||
"&::before": {
|
||||
left: "1px !important",
|
||||
width: "0px !important",
|
||||
marginLeft: "0 !important",
|
||||
},
|
||||
"&::after": {
|
||||
top: "0 !important",
|
||||
height: "100% !important",
|
||||
left: "var(--splitter-hover-adjustment) !important",
|
||||
width: "var(--splitter-hover-size) !important",
|
||||
},
|
||||
},
|
||||
".splitpanes--horizontal>.splitpanes__splitter": {
|
||||
height: "1px !important",
|
||||
"&::before": {
|
||||
top: "1px !important",
|
||||
height: "0px !important",
|
||||
marginTop: "0 !important",
|
||||
},
|
||||
"&::after": {
|
||||
top: "var(--splitter-hover-adjustment) !important",
|
||||
height: "var(--splitter-hover-size) !important",
|
||||
left: "0 !important",
|
||||
width: "100% !important",
|
||||
},
|
||||
},
|
||||
// Windmill Tab classes
|
||||
|
||||
".wm-tab-active": {
|
||||
borderColor: darkTheme.border,
|
||||
color: lightTheme.textPrimary,
|
||||
},
|
||||
|
||||
".dark .wm-tab-active": {
|
||||
borderColor: lightTheme.border,
|
||||
color: darkTheme.textPrimary,
|
||||
},
|
||||
});
|
||||
|
||||
addUtilities({
|
||||
".separator": {
|
||||
backgroundColor: `${lightTheme.border} !important`,
|
||||
},
|
||||
".dark .separator": {
|
||||
backgroundColor: `${darkTheme.border} !important`,
|
||||
},
|
||||
".center-center": {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
".inner-border": {
|
||||
boxShadow: `inset 0 0 0 1px ${lightTheme.border}`,
|
||||
},
|
||||
".dark .inner-border": {
|
||||
boxShadow: `inset 0 0 0 1px ${darkTheme.border}`,
|
||||
},
|
||||
".z5000": {
|
||||
zIndex: "5000 !important",
|
||||
},
|
||||
".ellipsize": {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
/** Set the '-webkit-line-clamp' property to the desired number of lines.
|
||||
*
|
||||
* Eg.: `class="ellipsize-multi-line [-webkit-line-clamp:3]"`
|
||||
*/
|
||||
".ellipsize-multi-line": {
|
||||
display: "-webkit-box",
|
||||
"-webkit-box-orient": "vertical",
|
||||
overflow: "hidden",
|
||||
},
|
||||
".disabled": {
|
||||
pointerEvents: "none",
|
||||
cursor: "default",
|
||||
filter: "grayscale(1)",
|
||||
},
|
||||
".scrollbar-hidden": {
|
||||
"-ms-overflow-style": "none",
|
||||
"scrollbar-width": "none",
|
||||
"&::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
width: "0px",
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
darkMode: "class",
|
||||
important: ".windmill-app",
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow_status",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "061ff848f258dc880bec81d923370c905e689f37c6d931ee4559c3cfd394e168"
|
||||
}
|
||||
@@ -55,7 +55,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
14
backend/.sqlx/query-15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069.json
generated
Normal file
14
backend/.sqlx/query-15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "15105be6247457fc01b7d65767ccdde047d0f0c172c7a01eeabe3bd8206a3069"
|
||||
}
|
||||
@@ -112,6 +112,11 @@
|
||||
"ordinal": 21,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -141,6 +146,7 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
14
backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json
generated
Normal file
14
backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,10 +11,11 @@
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Bool"
|
||||
"Bool",
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2e4115bb2e6c8c85ad1492ad135d6b0454b342126cb5fa17e58caf71b32ee755"
|
||||
"hash": "2be66f23536223549db9b50025932b6b1bad90b8fa47d97acb7d75aa3c37ef86"
|
||||
}
|
||||
20
backend/.sqlx/query-4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77.json
generated
Normal file
20
backend/.sqlx/query-4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4e6e4ecaa7a874b07b6599b6b7b3f307eb201534aa25d8a2049d1949f4ec9e77"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')",
|
||||
"query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -10,11 +10,13 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2ae96e8af2dddd533a0377983ec36e30091089d0a96a37d8c625ddaf85d16a83"
|
||||
"hash": "51a7e36ba1cc1616a19485eb5930cf90df758e85a8cac6037d044d5bd7440681"
|
||||
}
|
||||
@@ -112,6 +112,11 @@
|
||||
"ordinal": 21,
|
||||
"name": "default_scripts",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "deploy_ui",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -141,6 +146,7 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57d830e07e40317150b24cf78248480c3a8b33e3e240385cae5703ccb9b14bec"
|
||||
}
|
||||
@@ -69,7 +69,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"name": "bool",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
23
backend/.sqlx/query-8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b.json
generated
Normal file
23
backend/.sqlx/query-8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8fef781ecef8bb7e4f5172adbab1d1eefa5cf05ff6521ee605af7ac2b90fa60b"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(created_at) FROM completed_job",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47"
|
||||
}
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT worker, worker_instance, vcpus, memory, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days'",
|
||||
"query": "SELECT worker, worker_instance, vcpus, memory, ping_at, started_at FROM worker_ping WHERE ping_at > now() - interval '30 days' ORDER BY started_at",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -46,5 +46,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "43cc4e7d9846dbce81b61a25e8ceca636a08451db3288969ed9105fa0cb4e5b8"
|
||||
"hash": "a51007ca7b509b92faa6fe7aa59fa738594a2b7eaab8e38f3ca30e548f9a49a7"
|
||||
}
|
||||
12
backend/.sqlx/query-a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26.json
generated
Normal file
12
backend/.sqlx/query-a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a7d107fbeb8749d16ea3fea091573324cd5cbae2c3b77f24027802ea3896ef26"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_user_count as (SELECT count(email)::INT as count from active_users),\n active_author_count as (SELECT count(distinct email)::INT as count FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users))\n SELECT active_author_count.count as author_count, active_user_count.count - active_author_count.count as operator_count FROM active_user_count, active_author_count",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ad8d9c00bd2d3515ad4afa87aa24b243615484bbad5a793438e23908c3f20c2f"
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
20
backend/.sqlx/query-bac12eff366f6b76f97d1a44041e0c1b5fdcb2c4205c1c1c0026b71ce404f089.json
generated
Normal file
20
backend/.sqlx/query-bac12eff366f6b76f97d1a44041e0c1b5fdcb2c4205c1c1c0026b71ce404f089.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at > now() RETURNING path",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bac12eff366f6b76f97d1a44041e0c1b5fdcb2c4205c1c1c0026b71ce404f089"
|
||||
}
|
||||
15
backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json
generated
Normal file
15
backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5"
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT leaf_jobs->$1::text as leaf_jobs, parent_job FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "leaf_jobs",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "parent_job",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c8ac658002423906f2a6e43e423a9e24561dfaa6b07ce89063d0910d2342a83b"
|
||||
}
|
||||
38
backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json
generated
Normal file
38
backend/.sqlx/query-cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c.json
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "authors",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operators",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c"
|
||||
}
|
||||
@@ -75,7 +75,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
"graphql",
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php"
|
||||
"php",
|
||||
"bunnative"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id FROM queue WHERE created_at <= $1",
|
||||
"query": "SELECT id FROM queue",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -10,13 +10,11 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz"
|
||||
]
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890"
|
||||
"hash": "e207bbcdd478758562e3082330836d6efe1dd6c4f719bc415222eba78d5e63fc"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
"query": "UPDATE script SET lock = $1, created_at = now() WHERE hash = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8"
|
||||
"hash": "ec47955683d811b12e82ff3e6aeafa0df46a320c436bad5eb1acc127df138a61"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false\n GROUP BY script_path), $3) as min_started_at, now() AS now",
|
||||
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM queue\n WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -26,5 +26,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "3901cce744c9b246b661c817e068bdb3b1ab504ff8070fcccf6c909ad75f1f6d"
|
||||
"hash": "f3d20b0fa17836538ec93b84b08ffc6b555371d6eeeadfc54f5bf0bcbe93d8b4"
|
||||
}
|
||||
14
backend/.sqlx/query-f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225.json
generated
Normal file
14
backend/.sqlx/query-f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f4942f502ba7f66bafb0b2de6979601723de8092796f3bbeb78191c1da30d225"
|
||||
}
|
||||
980
backend/Cargo.lock
generated
980
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.368.0"
|
||||
version = "1.377.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.368.0"
|
||||
version = "1.377.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -54,6 +54,7 @@ openidconnect = ["windmill-api/openidconnect"]
|
||||
cloud = ["windmill-queue/cloud", "windmill-worker/cloud"]
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -155,7 +156,7 @@ mail-send = { version = "0.4.0", features = ["builder"], default-features=false
|
||||
urlencoding = "^2"
|
||||
url = "^2"
|
||||
async-oauth2 = "^0"
|
||||
reqwest = { version = "^0.12", features = ["json", "stream"] }
|
||||
reqwest = { version = "^0.12", features = ["json", "stream", "gzip"] }
|
||||
time = "0.3.16"
|
||||
serde_urlencoded = "^0"
|
||||
tokio-tar = "^0"
|
||||
@@ -164,21 +165,22 @@ tokio-util = { version = "^0", features = ["io"] }
|
||||
json-pointer = "^0"
|
||||
itertools = "^0"
|
||||
regex = "^1"
|
||||
deno_fetch = "0.182.0"
|
||||
deno_tls = "0.145.0"
|
||||
deno_console = "0.158.0"
|
||||
deno_url = "0.158.0"
|
||||
deno_webidl = "0.158.0"
|
||||
deno_web = "0.189.0"
|
||||
deno_net = "0.150.0"
|
||||
deno_core = "0.290.0"
|
||||
deno_ast = { version = "=0.39.2", features = ["transpiling"] }
|
||||
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"] }
|
||||
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"
|
||||
sha2 = "0.10.6"
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
@@ -240,6 +242,9 @@ candle-nn = "0.3.0"
|
||||
tiberius = { git = "https://github.com/prisma/tiberius", rev = "8f66a699dfa041e7b5f736c7e94f92c945453c9e", default-features = false, features = ["rustls", "tds73", "chrono", "sql-browser-tokio"]}
|
||||
pin-project = "1"
|
||||
indexmap = { version = "2.2.5", features = ["serde"]}
|
||||
tokio-native-tls = "^0"
|
||||
openssl = "=0.10"
|
||||
mail-parser = "^0"
|
||||
|
||||
datafusion = "39.0.0"
|
||||
object_store = { version = "0.10.0", features = ["aws", "azure"] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
a64d2990e733aba3f8cbc6691a6090bcd481c891
|
||||
80d9e507977b55d0b76272905c969c3d6e830083
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1 @@
|
||||
CREATE INDEX IF NOT EXISTS queue_concurrency_started_at ON queue (workspace_id, script_path, started_at) WHERE concurrent_limit > 0 AND running = true AND job_kind != 'dependencies' AND canceled = false;-- Add up migration script here
|
||||
1
backend/migrations/20240729064122_addbunnative.down.sql
Normal file
1
backend/migrations/20240729064122_addbunnative.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20240729064122_addbunnative.up.sql
Normal file
2
backend/migrations/20240729064122_addbunnative.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'bunnative';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
alter table queue drop constraint if exists queue_workspace_id_fkey;
|
||||
alter table completed_job drop constraint if exists completed_job_workspace_id_fkey;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
4
backend/migrations/20240731213058_improve_indices.up.sql
Normal file
4
backend/migrations/20240731213058_improve_indices.up.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
DROP INDEX IF EXISTS queue_sort;
|
||||
CREATE INDEX IF NOT EXISTS queue_sort ON queue (running, tag, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;
|
||||
CREATE INDEX IF NOT EXISTS queue_sort_2 ON queue (running, priority DESC NULLS LAST, scheduled_for, created_at) WHERE running = false;
|
||||
2
backend/migrations/20240801084701_add_expiry.down.sql
Normal file
2
backend/migrations/20240801084701_add_expiry.down.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE variable DROP COLUMN IF EXISTS expires_at;
|
||||
3
backend/migrations/20240801084701_add_expiry.up.sql
Normal file
3
backend/migrations/20240801084701_add_expiry.up.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE variable ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP WITH TIME ZONE;
|
||||
UPDATE variable SET expires_at = (now() + '7 days'::interval) WHERE path LIKE 'u/%/secret_arg/%'
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE workspace_settings DROP COLUMN deploy_ui;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS deploy_ui JSONB;
|
||||
@@ -336,12 +336,12 @@ pub fn parse_sql_statement_named_params(code: &str, prefix: char) -> HashSet<Str
|
||||
char == prefix
|
||||
&& chars
|
||||
.peek()
|
||||
.is_some_and(|&(_, next_char)| next_char.is_alphanumeric())
|
||||
.is_some_and(|&(_, next_char)| next_char.is_alphanumeric() || next_char == '_')
|
||||
},
|
||||
|_, chars| {
|
||||
let mut arg_name = String::new();
|
||||
while let Some(&(_, char)) = chars.peek() {
|
||||
if char.is_alphanumeric() {
|
||||
if char.is_alphanumeric() || char == '_' {
|
||||
arg_name.push(char);
|
||||
chars.next();
|
||||
} else {
|
||||
@@ -751,8 +751,8 @@ SELECT ?, ?;
|
||||
let code = r#"
|
||||
-- :param1 (int) = 3
|
||||
-- :param2 (text)
|
||||
-- :param3 (text)
|
||||
SELECT :param3, :param1;
|
||||
-- :param_3 (text)
|
||||
SELECT :param_3, :param1;
|
||||
SELECT :param2;
|
||||
"#;
|
||||
assert_eq!(
|
||||
@@ -779,7 +779,7 @@ SELECT :param2;
|
||||
},
|
||||
Arg {
|
||||
otyp: Some("text".to_string()),
|
||||
name: "param3".to_string(),
|
||||
name: "param_3".to_string(),
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
|
||||
@@ -405,6 +405,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
server_killpill_rx,
|
||||
base_internal_tx,
|
||||
server_mode,
|
||||
base_internal_url.clone(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -551,14 +552,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
load_require_preexisting_user(&db).await;
|
||||
},
|
||||
EXPOSE_METRICS_SETTING => {
|
||||
if worker_mode {
|
||||
tracing::info!("Metrics setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all serverss and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..4);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
}
|
||||
tracing::info!("Metrics setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..40);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
}
|
||||
},
|
||||
EXPOSE_DEBUG_METRICS_SETTING => {
|
||||
@@ -792,6 +791,10 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
.expect("could not create initial worker dir");
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
|
||||
*windmill_worker::SLEEP_QUEUE
|
||||
);
|
||||
for i in 1..(num_workers + 1) {
|
||||
let db1 = db.clone();
|
||||
let instance_name = instance_name.clone();
|
||||
@@ -804,7 +807,9 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
let hostname = hostname.clone();
|
||||
|
||||
handles.push(tokio::spawn(async move {
|
||||
tracing::info!(worker = %worker_name, "starting worker");
|
||||
if num_workers > 1 {
|
||||
tracing::info!(worker = %worker_name, "starting worker {i}");
|
||||
}
|
||||
|
||||
let f = windmill_worker::run_worker(
|
||||
&db1,
|
||||
|
||||
@@ -42,8 +42,8 @@ use windmill_common::{
|
||||
users::truncate_token,
|
||||
utils::{now_from_db, rd_string},
|
||||
worker::{
|
||||
load_worker_config, reload_custom_tags_setting, DEFAULT_TAGS_PER_WORKSPACE,
|
||||
DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
|
||||
load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting,
|
||||
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
|
||||
},
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED,
|
||||
@@ -406,6 +406,21 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
|
||||
}
|
||||
|
||||
let deleted_expired_variables = sqlx::query_scalar!(
|
||||
"DELETE FROM variable WHERE expires_at IS NOT NULL AND expires_at > now() RETURNING path",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match deleted_expired_variables {
|
||||
Ok(res) => {
|
||||
if res.len() > 0 {
|
||||
tracing::info!("deleted {} expired variables {:?}", res.len(), res)
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::error!("Error deleting cache resource {}", e.to_string()),
|
||||
}
|
||||
|
||||
let job_retention_secs = *JOB_RETENTION_SECS.read().await;
|
||||
if job_retention_secs > 0 {
|
||||
match db.begin().await {
|
||||
@@ -948,6 +963,8 @@ pub async fn reload_worker_config(
|
||||
|
||||
let mut wc = WORKER_CONFIG.write().await;
|
||||
tracing::info!("Reloading worker config...");
|
||||
make_suspended_pull_query(&config).await;
|
||||
make_pull_query(&config).await;
|
||||
*wc = config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use windmill_api_client::types::{EditSchedule, NewSchedule, ScriptArgs};
|
||||
|
||||
use serde::Serialize;
|
||||
use windmill_common::auth::JWT_SECRET;
|
||||
use windmill_common::worker::{PriorityTags, WORKER_CONFIG};
|
||||
use windmill_common::worker::WORKER_CONFIG;
|
||||
use windmill_common::{
|
||||
flow_status::{FlowStatus, FlowStatusModule, RestartedFrom},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
|
||||
@@ -128,6 +128,7 @@ impl ApiServer {
|
||||
rx,
|
||||
port_tx,
|
||||
false,
|
||||
format!("http://localhost:{}", addr.port()),
|
||||
));
|
||||
|
||||
_port_rx.await.unwrap();
|
||||
@@ -1007,8 +1008,12 @@ fn spawn_test_worker(
|
||||
{
|
||||
let mut wc = WORKER_CONFIG.write().await;
|
||||
(*wc).worker_tags = windmill_common::worker::DEFAULT_TAGS.clone();
|
||||
(*wc).priority_tags_sorted =
|
||||
vec![PriorityTags { priority: 0, tags: (*wc).worker_tags.clone() }]
|
||||
(*wc).priority_tags_sorted = vec![windmill_common::worker::PriorityTags {
|
||||
priority: 0,
|
||||
tags: (*wc).worker_tags.clone(),
|
||||
}];
|
||||
windmill_common::worker::make_suspended_pull_query(&wc).await;
|
||||
windmill_common::worker::make_pull_query(&wc).await;
|
||||
}
|
||||
windmill_worker::run_worker::<rsmq_async::MultiplexedRsmq>(
|
||||
&db,
|
||||
|
||||
@@ -51,6 +51,7 @@ chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
hex.workspace = true
|
||||
base64.workspace = true
|
||||
base32.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
cron.workspace = true
|
||||
mime_guess.workspace = true
|
||||
@@ -59,6 +60,10 @@ tracing-subscriber.workspace = true
|
||||
quick_cache.workspace = true
|
||||
rand.workspace = true
|
||||
time.workspace = true
|
||||
native-tls.workspace = true
|
||||
tokio-native-tls.workspace = true
|
||||
openssl.workspace = true
|
||||
mail-parser = { workspace = true, features = ["serde_support"] }
|
||||
magic-crypt.workspace = true
|
||||
tempfile.workspace = true
|
||||
tokio-util.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.368.0
|
||||
version: 1.377.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -845,6 +845,12 @@ paths:
|
||||
operationId: renewLicenseKey
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- name: license_key
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
@@ -859,6 +865,12 @@ paths:
|
||||
operationId: createCustomerPortalSession
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- name: license_key
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: url to portal
|
||||
@@ -1507,6 +1519,8 @@ paths:
|
||||
$ref: "#/components/schemas/LargeFileStorage"
|
||||
git_sync:
|
||||
$ref: "#/components/schemas/WorkspaceGitSyncSettings"
|
||||
deploy_ui:
|
||||
$ref: "#/components/schemas/WorkspaceDeployUISettings"
|
||||
default_app:
|
||||
type: string
|
||||
default_scripts:
|
||||
@@ -1896,6 +1910,32 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/edit_deploy_ui_config:
|
||||
post:
|
||||
summary: edit workspace deploy ui settings
|
||||
operationId: editWorkspaceDeployUISettings
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: Workspace deploy UI settings
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deploy_ui_settings:
|
||||
$ref: "#/components/schemas/WorkspaceDeployUISettings"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/workspaces/edit_default_app:
|
||||
post:
|
||||
summary: edit default app for workspace
|
||||
@@ -2444,6 +2484,8 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
"200":
|
||||
description: variable list
|
||||
@@ -9770,6 +9812,9 @@ components:
|
||||
type: boolean
|
||||
is_refreshed:
|
||||
type: boolean
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- workspace_id
|
||||
- path
|
||||
@@ -9808,6 +9853,9 @@ components:
|
||||
type: integer
|
||||
is_oauth:
|
||||
type: boolean
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -11070,6 +11118,25 @@ components:
|
||||
items:
|
||||
$ref: "#/components/schemas/GitRepositorySettings"
|
||||
|
||||
WorkspaceDeployUISettings:
|
||||
type: object
|
||||
properties:
|
||||
include_path:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
include_type:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- script
|
||||
- flow
|
||||
- app
|
||||
- resource
|
||||
- variable
|
||||
- secret
|
||||
|
||||
WorkspaceDefaultScripts:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -29,6 +29,7 @@ use sha2::{Digest, Sha256};
|
||||
use sql_builder::{bind::Bind, SqlBuilder};
|
||||
use sqlx::{types::Uuid, FromRow};
|
||||
use std::str;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
@@ -65,10 +66,24 @@ pub fn workspaced_service() -> Router {
|
||||
}
|
||||
|
||||
pub fn unauthed_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET])
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
Router::new()
|
||||
.route("/execute_component/*path", post(execute_component))
|
||||
.route("/public_app/:secret", get(get_public_app_by_secret))
|
||||
.route("/public_resource/*path", get(get_public_resource))
|
||||
.route(
|
||||
"/execute_component/*path",
|
||||
post(execute_component).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/public_app/:secret",
|
||||
get(get_public_app_by_secret).layer(cors.clone()),
|
||||
)
|
||||
.route(
|
||||
"/public_resource/*path",
|
||||
get(get_public_resource).layer(cors.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -1383,7 +1398,30 @@ async fn build_args(
|
||||
|
||||
let arg_str = v.get();
|
||||
|
||||
if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
if arg_str.starts_with("\"$ctx:") {
|
||||
let prop = arg_str.trim_start_matches("\"$ctx:").trim_end_matches("\"");
|
||||
let value = match prop {
|
||||
"username" => authed.as_ref().map(|a| {
|
||||
serde_json::to_value(a.username_override.as_ref().unwrap_or(&a.username))
|
||||
}),
|
||||
"email" => authed.as_ref().map(|a| serde_json::to_value(&a.email)),
|
||||
"workspace" => Some(serde_json::to_value(&w_id)),
|
||||
"groups" => authed.as_ref().map(|a| serde_json::to_value(&a.groups)),
|
||||
"author" => Some(serde_json::to_value(&policy.on_behalf_of_email)),
|
||||
_ => {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"context variable {} not allowed",
|
||||
prop
|
||||
)))
|
||||
}
|
||||
};
|
||||
safe_args.insert(
|
||||
k.to_string(),
|
||||
to_raw_value(&value.unwrap_or(Ok(serde_json::Value::Null)).map_err(|e| {
|
||||
Error::InternalErr(format!("failed to serialize ctx variable for {}: {}", k, e))
|
||||
})?),
|
||||
);
|
||||
} else if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
safe_args.insert(k.to_string(), v);
|
||||
} else {
|
||||
safe_args.insert(
|
||||
|
||||
@@ -249,14 +249,57 @@ async fn fix_flow_versioning_migration(
|
||||
}
|
||||
|
||||
async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
// let has_done_migration = sqlx::query_scalar!(
|
||||
// "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
|
||||
// )
|
||||
// .fetch_one(db)
|
||||
// .await?
|
||||
// .unwrap_or(false);
|
||||
// if !has_done_migration {
|
||||
// tracing::info!("Applying fix_job_completed_index migration");
|
||||
// let mut tx = db.begin().await?;
|
||||
// let mut r = false;
|
||||
// while !r {
|
||||
// r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
// .fetch_one(&mut *tx)
|
||||
// .await
|
||||
// .map_err(|e| {
|
||||
// tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
|
||||
// sqlx::migrate::MigrateError::Execute(e)
|
||||
// })?
|
||||
// .unwrap_or(false);
|
||||
// if !r {
|
||||
// tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
// tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
// }
|
||||
// }
|
||||
// // sqlx::query(
|
||||
// // "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
|
||||
// // ).execute(db).await?;
|
||||
|
||||
// sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
|
||||
// .execute(db)
|
||||
// .await?;
|
||||
|
||||
// sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
|
||||
// .execute(&mut *tx)
|
||||
// .await?;
|
||||
// let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
// .execute(&mut *tx)
|
||||
// .await?;
|
||||
// tx.commit().await?;
|
||||
// }
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_2";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying fix_job_completed_index migration");
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
@@ -264,30 +307,266 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring fix_job_completed_index lock: {e:#}");
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG fix_job_completed_index_migration lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
sqlx::query(
|
||||
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new ON completed_job (workspace_id, job_kind, is_skipped, is_flow_step, created_at DESC, started_at DESC)"
|
||||
).execute(db).await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
// sqlx::query(
|
||||
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
|
||||
// ).execute(db).await?;
|
||||
|
||||
// sqlx::query(
|
||||
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
|
||||
// ).execute(db).await?;
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('fix_job_completed_index') ON CONFLICT DO NOTHING")
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_3";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_queue")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_on_created")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
let migration_job_name = "fix_job_completed_index_4";
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !has_done_migration {
|
||||
tracing::info!("Applying {migration_job_name} migration");
|
||||
let mut tx = db.begin().await?;
|
||||
let mut r = false;
|
||||
while !r {
|
||||
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
|
||||
sqlx::migrate::MigrateError::Execute(e)
|
||||
})?
|
||||
.unwrap_or(false);
|
||||
if !r {
|
||||
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
tracing::info!("acquired lock for {migration_job_name}");
|
||||
|
||||
let has_done_migration = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
|
||||
migration_job_name
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_done_migration {
|
||||
let mut i = 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query(
|
||||
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
i += 1;
|
||||
tracing::info!("step {i} of {migration_job_name} migration");
|
||||
|
||||
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
|
||||
migration_job_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tracing::info!("Finished applying {migration_job_name} migration");
|
||||
} else {
|
||||
tracing::info!("migration {migration_job_name} already done");
|
||||
}
|
||||
|
||||
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
tracing::info!("released lock for {migration_job_name}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -184,7 +184,10 @@ pub fn workspaced_service() -> Router {
|
||||
.layer(ce_headers.clone()),
|
||||
)
|
||||
.route("/run/preview", post(run_preview_script))
|
||||
.route("/run/preview_bundle", post(run_bundle_preview_script))
|
||||
.route(
|
||||
"/run/preview_bundle",
|
||||
post(run_bundle_preview_script).layer(axum::extract::DefaultBodyLimit::disable()),
|
||||
)
|
||||
.route("/add_batch_jobs/:n", post(add_batch_jobs))
|
||||
.route("/run/preview_flow", post(run_preview_flow_job))
|
||||
.route(
|
||||
@@ -1031,7 +1034,7 @@ pub struct ListableCompletedJob {
|
||||
pub labels: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[derive(Deserialize, Clone, Default)]
|
||||
pub struct RunJobQuery {
|
||||
scheduled_for: Option<chrono::DateTime<chrono::Utc>>,
|
||||
scheduled_in_secs: Option<i64>,
|
||||
@@ -1393,7 +1396,7 @@ async fn cancel_jobs(
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM queue WHERE id = any($1) AND workspace_id = $2",
|
||||
&jobs,
|
||||
&trivial_jobs,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -1459,7 +1462,6 @@ async fn cancel_selection(
|
||||
Path(w_id): Path<String>,
|
||||
Json(jobs): Json<Vec<Uuid>>,
|
||||
) -> error::JsonResult<Vec<Uuid>> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let jobs_to_cancel = sqlx::query_scalar!(
|
||||
@@ -2611,6 +2613,7 @@ enum PreviewKind {
|
||||
Http,
|
||||
Noop,
|
||||
Bundle,
|
||||
Tarbundle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -2748,6 +2751,23 @@ pub async fn run_flow_by_path(
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_flow_by_path_inner(
|
||||
authed, db, user_db, rsmq, w_id, flow_path, run_query, args, None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_flow_by_path_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
flow_path: StripPath,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -2781,7 +2801,9 @@ pub async fn run_flow_by_path(
|
||||
&w_id,
|
||||
JobPayload::Flow { path: flow_path.to_string(), dedicated_worker },
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -2907,6 +2929,31 @@ pub async fn run_script_by_path(
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_script_by_path_inner(
|
||||
authed,
|
||||
db,
|
||||
user_db,
|
||||
rsmq,
|
||||
w_id,
|
||||
script_path,
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_script_by_path_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
script_path: StripPath,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -2932,7 +2979,9 @@ pub async fn run_script_by_path(
|
||||
&w_id,
|
||||
job_payload,
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -3810,6 +3859,8 @@ async fn run_bundle_preview_script(
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
use windmill_common::scripts::PREVIEW_IS_TAR_CODEBASE_HASH;
|
||||
|
||||
check_license_key_valid().await?;
|
||||
|
||||
check_scopes(&authed, || format!("runscript"))?;
|
||||
@@ -3822,9 +3873,12 @@ async fn run_bundle_preview_script(
|
||||
let mut job_id = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
let mut is_tar = false;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
let data = field.bytes().await;
|
||||
let data = data.map_err(to_anyhow)?;
|
||||
if name == "preview" {
|
||||
let preview: Preview = serde_json::from_slice(&data).map_err(to_anyhow)?;
|
||||
|
||||
@@ -3836,27 +3890,33 @@ async fn run_bundle_preview_script(
|
||||
|
||||
let args = preview.args.unwrap_or_default();
|
||||
|
||||
is_tar = match preview.kind {
|
||||
Some(PreviewKind::Tarbundle) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// tracing::info!("is_tar 1: {is_tar}");
|
||||
// hmap.insert("")
|
||||
let (uuid, ntx) = push(
|
||||
&db,
|
||||
ltx,
|
||||
&w_id,
|
||||
match preview.kind {
|
||||
Some(PreviewKind::Identity) => JobPayload::Identity,
|
||||
Some(PreviewKind::Noop) => JobPayload::Noop,
|
||||
_ => JobPayload::Code(RawCode {
|
||||
hash: Some(PREVIEW_IS_CODEBASE_HASH),
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
lock: preview.lock,
|
||||
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
|
||||
concurrency_time_window_s: None, // TODO(gbouv): same as above
|
||||
cache_ttl: None,
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
custom_concurrency_key: None,
|
||||
}),
|
||||
},
|
||||
JobPayload::Code(RawCode {
|
||||
hash: if is_tar {
|
||||
Some(PREVIEW_IS_TAR_CODEBASE_HASH)
|
||||
} else {
|
||||
Some(PREVIEW_IS_CODEBASE_HASH)
|
||||
},
|
||||
content: preview.content.unwrap_or_default(),
|
||||
path: preview.path,
|
||||
language: preview.language.unwrap_or(ScriptLang::Deno),
|
||||
lock: preview.lock,
|
||||
concurrent_limit: None, // TODO(gbouv): once I find out how to store limits in the content of a script, should be easy to plug limits here
|
||||
concurrency_time_window_s: None, // TODO(gbouv): same as above
|
||||
cache_ttl: None,
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
custom_concurrency_key: None,
|
||||
}),
|
||||
PushArgs::from(&args),
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
@@ -3881,7 +3941,7 @@ async fn run_bundle_preview_script(
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
let id = job_id
|
||||
let mut id = job_id
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
@@ -3890,6 +3950,12 @@ async fn run_bundle_preview_script(
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
// tracing::info!("is_tar 2: {is_tar}");
|
||||
|
||||
if is_tar {
|
||||
id = format!("{}.tar", id);
|
||||
}
|
||||
|
||||
uploaded = true;
|
||||
|
||||
if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
@@ -4332,6 +4398,31 @@ pub async fn run_job_by_hash(
|
||||
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
args: PushArgsOwned,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
run_job_by_hash_inner(
|
||||
authed,
|
||||
db,
|
||||
user_db,
|
||||
rsmq,
|
||||
w_id,
|
||||
script_hash,
|
||||
run_query,
|
||||
args,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_job_by_hash_inner(
|
||||
authed: ApiAuthed,
|
||||
db: DB,
|
||||
user_db: UserDB,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
w_id: String,
|
||||
script_hash: ScriptHash,
|
||||
run_query: RunJobQuery,
|
||||
args: PushArgsOwned,
|
||||
label_prefix: Option<String>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
check_license_key_valid().await?;
|
||||
@@ -4378,7 +4469,9 @@ pub async fn run_job_by_hash(
|
||||
priority,
|
||||
},
|
||||
PushArgs { args: &args.args, extra: args.extra },
|
||||
authed.display_username(),
|
||||
&label_prefix
|
||||
.map(|x| x + authed.display_username())
|
||||
.unwrap_or_else(|| authed.display_username().to_string()),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
@@ -5003,7 +5096,7 @@ async fn delete_completed_job<'a>(
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let job_o = sqlx::query_as::<_, CompletedJob>(
|
||||
"UPDATE completed_job SET args = null, logs = '', result = null, deleted = true WHERE id = $1 AND workspace_id = $2 \
|
||||
RETURNING *",
|
||||
RETURNING *, null as labels",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(&w_id)
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::ee::ExternalJwks;
|
||||
#[cfg(feature = "embedding")]
|
||||
use crate::embeddings::load_embeddings_db;
|
||||
use crate::oauth2_ee::AllClients;
|
||||
use crate::smtp_server_ee::SmtpServer;
|
||||
use crate::tracing_init::MyOnFailure;
|
||||
use crate::{
|
||||
oauth2_ee::SlackVerifier,
|
||||
@@ -76,6 +77,7 @@ mod schedule;
|
||||
mod scim_ee;
|
||||
mod scripts;
|
||||
mod settings;
|
||||
pub mod smtp_server_ee;
|
||||
mod static_assets;
|
||||
mod stripe_ee;
|
||||
mod tracing_init;
|
||||
@@ -163,6 +165,7 @@ pub async fn run_server(
|
||||
mut rx: tokio::sync::broadcast::Receiver<()>,
|
||||
port_tx: tokio::sync::oneshot::Sender<String>,
|
||||
server_mode: bool,
|
||||
base_internal_url: String,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(mut rsmq) = rsmq.clone() {
|
||||
for tag in ALL_TAGS.read().await.iter() {
|
||||
@@ -194,8 +197,8 @@ pub async fn run_server(
|
||||
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
.layer(Extension(db.clone()))
|
||||
.layer(Extension(rsmq))
|
||||
.layer(Extension(user_db))
|
||||
.layer(Extension(rsmq.clone()))
|
||||
.layer(Extension(user_db.clone()))
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(Extension(index_reader))
|
||||
.layer(Extension(index_writer))
|
||||
@@ -214,7 +217,18 @@ pub async fn run_server(
|
||||
|
||||
if server_mode {
|
||||
#[cfg(feature = "embedding")]
|
||||
load_embeddings_db(&db)
|
||||
load_embeddings_db(&db);
|
||||
|
||||
let smtp_server = Arc::new(SmtpServer {
|
||||
db: db.clone(),
|
||||
user_db: user_db,
|
||||
auth_cache: auth_cache.clone(),
|
||||
rsmq: rsmq,
|
||||
base_internal_url: base_internal_url.clone(),
|
||||
});
|
||||
if let Err(err) = smtp_server.start_listener_thread(addr).await {
|
||||
tracing::error!("Error starting SMTP server: {err:#}");
|
||||
}
|
||||
}
|
||||
|
||||
let job_helpers_service = {
|
||||
@@ -320,7 +334,7 @@ pub async fn run_server(
|
||||
)
|
||||
.nest(
|
||||
"/w/:workspace_id/capture_u",
|
||||
capture::global_service().layer(cors),
|
||||
capture::global_service().layer(cors.clone()),
|
||||
)
|
||||
.nest(
|
||||
"/auth",
|
||||
@@ -332,7 +346,7 @@ pub async fn run_server(
|
||||
)
|
||||
.route("/version", get(git_v))
|
||||
.route("/uptodate", get(is_up_to_date))
|
||||
.route("/ee_license", get(ee_license))
|
||||
.route("/ee_license", get(ee_license).layer(cors.clone()))
|
||||
.route("/openapi.yaml", get(openapi))
|
||||
.route("/openapi.json", get(openapi_json)),
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ use windmill_common::{
|
||||
utils::{
|
||||
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
|
||||
},
|
||||
worker::to_raw_value,
|
||||
worker::{get_annotation, to_raw_value},
|
||||
HUB_BASE_URL,
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
@@ -352,13 +352,16 @@ async fn create_snapshot_script(
|
||||
let mut script_hash = None;
|
||||
let mut tx = None;
|
||||
let mut uploaded = false;
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap() {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
if name == "script" {
|
||||
let ns = Some(serde_json::from_slice(&data).map_err(to_anyhow)?);
|
||||
let ns: NewScript = Some(serde_json::from_slice(&data).map_err(to_anyhow)?).unwrap();
|
||||
let is_tar = ns.codebase.as_ref().is_some_and(|x| x.ends_with(".tar"));
|
||||
|
||||
let (new_hash, ntx) = create_script_internal(
|
||||
ns.unwrap(),
|
||||
ns,
|
||||
w_id.clone(),
|
||||
authed.clone(),
|
||||
db.clone(),
|
||||
@@ -367,7 +370,8 @@ async fn create_snapshot_script(
|
||||
webhook.clone(),
|
||||
)
|
||||
.await?;
|
||||
script_hash = Some(new_hash.to_string());
|
||||
let nh = new_hash.to_string();
|
||||
script_hash = Some(if is_tar { format!("{nh}.tar") } else { nh });
|
||||
tx = Some(ntx);
|
||||
}
|
||||
if name == "file" {
|
||||
@@ -567,9 +571,12 @@ async fn create_script_internal<'c>(
|
||||
.as_ref()
|
||||
.map(|v| v.perms.clone())
|
||||
.unwrap_or(json!({}));
|
||||
let lock = if !(ns.language == ScriptLang::Python3
|
||||
let lock = if ns.codebase.is_some() {
|
||||
Some(String::new())
|
||||
} else if !(ns.language == ScriptLang::Python3
|
||||
|| ns.language == ScriptLang::Go
|
||||
|| ns.language == ScriptLang::Bun
|
||||
|| ns.language == ScriptLang::Bunnative
|
||||
|| ns.language == ScriptLang::Deno
|
||||
|| ns.language == ScriptLang::Php)
|
||||
{
|
||||
@@ -578,13 +585,25 @@ async fn create_script_internal<'c>(
|
||||
ns.lock
|
||||
.and_then(|e| if e.is_empty() { None } else { Some(e) })
|
||||
};
|
||||
let needs_lock_gen = lock.is_none();
|
||||
|
||||
let needs_lock_gen = lock.is_none() && codebase.is_none();
|
||||
let envs = ns.envs.as_ref().map(|x| x.as_slice());
|
||||
let envs = if ns.envs.is_none() || ns.envs.as_ref().unwrap().is_empty() {
|
||||
None
|
||||
} else {
|
||||
envs
|
||||
};
|
||||
|
||||
let lang = if &ns.language == &ScriptLang::Bun || &ns.language == &ScriptLang::Bunnative {
|
||||
let anns = get_annotation(&ns.content);
|
||||
if anns.native_mode {
|
||||
ScriptLang::Bunnative
|
||||
} else {
|
||||
ScriptLang::Bun
|
||||
}
|
||||
} else {
|
||||
ns.language.clone()
|
||||
};
|
||||
sqlx::query!(
|
||||
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \
|
||||
content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \
|
||||
@@ -604,7 +623,7 @@ async fn create_script_internal<'c>(
|
||||
ns.is_template.unwrap_or(false),
|
||||
extra_perms,
|
||||
lock,
|
||||
ns.language.clone() as ScriptLang,
|
||||
lang as ScriptLang,
|
||||
ns.kind.unwrap_or(ScriptKind::Script) as ScriptKind,
|
||||
ns.tag,
|
||||
ns.draft_only,
|
||||
|
||||
@@ -24,7 +24,10 @@ use axum::{
|
||||
use serde::Deserialize;
|
||||
use windmill_common::{
|
||||
error::{self, JsonResult, Result},
|
||||
global_settings::{AUTOMATE_USERNAME_CREATION_SETTING, ENV_SETTINGS, HUB_BASE_URL_SETTING},
|
||||
global_settings::{
|
||||
AUTOMATE_USERNAME_CREATION_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
HUB_BASE_URL_SETTING,
|
||||
},
|
||||
server::Smtp,
|
||||
utils::send_email,
|
||||
};
|
||||
@@ -256,6 +259,7 @@ pub async fn get_global_setting(
|
||||
&& !key.starts_with("default_recovery_handler_")
|
||||
&& key != AUTOMATE_USERNAME_CREATION_SETTING
|
||||
&& key != HUB_BASE_URL_SETTING
|
||||
&& key != EMAIL_DOMAIN_SETTING
|
||||
{
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
}
|
||||
@@ -346,7 +350,7 @@ pub async fn renew_license_key() -> Result<String> {
|
||||
pub async fn renew_license_key(Extension(db): Extension<DB>, authed: ApiAuthed) -> Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
windmill_common::stats_ee::send_stats(&"manual".to_string(), &HTTP_CLIENT, &db).await?;
|
||||
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db).await;
|
||||
let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db, None).await;
|
||||
|
||||
if result != "success" {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
@@ -367,7 +371,7 @@ pub async fn create_customer_portal_session() -> Result<String> {
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn create_customer_portal_session() -> Result<String> {
|
||||
let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT).await?;
|
||||
let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, None).await?;
|
||||
|
||||
return Ok(url);
|
||||
}
|
||||
|
||||
17
backend/windmill-api/src/smtp_server_ee.rs
Normal file
17
backend/windmill-api/src/smtp_server_ee.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use crate::{db::DB, users::AuthCache};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use windmill_common::db::UserDB;
|
||||
|
||||
pub struct SmtpServer {
|
||||
pub auth_cache: Arc<AuthCache>,
|
||||
pub db: DB,
|
||||
pub user_db: UserDB,
|
||||
pub rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
pub base_internal_url: String,
|
||||
}
|
||||
|
||||
impl SmtpServer {
|
||||
pub async fn start_listener_thread(self: Arc<Self>, _addr: SocketAddr) -> anyhow::Result<()> {
|
||||
Err(anyhow::anyhow!("Implementation not open source"))
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ use time::OffsetDateTime;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use tokio::sync::RwLock;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::{Instrument, Span};
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
@@ -66,6 +67,11 @@ const COOKIE_NAME: &str = "token";
|
||||
const COOKIE_PATH: &str = "/";
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET])
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
Router::new()
|
||||
.route("/list", get(list_users))
|
||||
.route("/list_usage", get(list_user_usage))
|
||||
@@ -76,16 +82,21 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/delete/:user", delete(delete_workspace_user))
|
||||
.route("/is_owner/*path", get(is_owner_of_path))
|
||||
.route("/whois/:username", get(whois))
|
||||
.route("/whoami", get(whoami))
|
||||
.route("/whoami", get(whoami).layer(cors))
|
||||
.route("/leave", post(leave_workspace))
|
||||
.route("/username_to_email/:username", get(username_to_email))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET])
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
Router::new()
|
||||
.route("/exists/:email", get(exists_email))
|
||||
.route("/email", get(get_email))
|
||||
.route("/whoami", get(global_whoami))
|
||||
.route("/whoami", get(global_whoami).layer(cors))
|
||||
.route("/list_invites", get(list_invites))
|
||||
.route("/decline_invite", post(decline_invite))
|
||||
.route("/accept_invite", post(accept_invite))
|
||||
@@ -118,8 +129,13 @@ pub fn global_service() -> Router {
|
||||
}
|
||||
|
||||
pub fn make_unauthed_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::POST])
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
Router::new()
|
||||
.route("/login", post(login))
|
||||
.route("/login", post(login).layer(cors))
|
||||
.route("/logout", post(logout))
|
||||
.route("/logout", get(logout))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
utils::{not_found_if_none, StripPath},
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath},
|
||||
variables::{
|
||||
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
|
||||
},
|
||||
@@ -92,7 +92,10 @@ async fn list_variables(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(lq): Query<ListVariableQuery>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
) -> JsonResult<Vec<ListableVariable>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let rows = sqlx::query_as::<_, ListableVariable>(
|
||||
@@ -100,17 +103,22 @@ async fn list_variables(
|
||||
is_secret, variable.description, variable.extra_perms, account, is_oauth, (now() > account.expires_at) as is_expired,
|
||||
account.refresh_error,
|
||||
resource.path IS NOT NULL as is_linked,
|
||||
account.refresh_token != '' as is_refreshed
|
||||
account.refresh_token != '' as is_refreshed,
|
||||
variable.expires_at
|
||||
from variable
|
||||
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
|
||||
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
|
||||
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%'
|
||||
AND variable.path LIKE $3 || '%'
|
||||
ORDER BY path",
|
||||
ORDER BY path
|
||||
LIMIT $4 OFFSET $5
|
||||
",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.bind(&authed.username)
|
||||
.bind(&lq.path_start.unwrap_or_default())
|
||||
.bind(per_page as i32)
|
||||
.bind(offset as i32)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -310,8 +318,8 @@ async fn create_variable(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
(workspace_id, path, value, is_secret, description, account, is_oauth, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
&w_id,
|
||||
variable.path,
|
||||
value,
|
||||
@@ -319,6 +327,7 @@ async fn create_variable(
|
||||
variable.description,
|
||||
variable.account,
|
||||
variable.is_oauth.unwrap_or(false),
|
||||
variable.expires_at
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -21,6 +21,8 @@ use crate::{
|
||||
webhook_util::WebhookShared,
|
||||
};
|
||||
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
response::IntoResponse,
|
||||
@@ -42,7 +44,7 @@ use windmill_common::schedule::Schedule;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::variables::build_crypt;
|
||||
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
|
||||
use windmill_common::workspaces::WorkspaceGitSyncSettings;
|
||||
use windmill_common::workspaces::{WorkspaceDeploymentUISettings, WorkspaceGitSyncSettings};
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
@@ -97,6 +99,7 @@ pub fn workspaced_service() -> Router {
|
||||
post(edit_large_file_storage_config),
|
||||
)
|
||||
.route("/edit_git_sync_config", post(edit_git_sync_config))
|
||||
.route("/edit_deploy_ui_config", post(edit_deploy_ui_config))
|
||||
.route("/edit_default_app", post(edit_default_app))
|
||||
.route("/default_app", get(get_default_app))
|
||||
.route(
|
||||
@@ -123,10 +126,15 @@ pub fn workspaced_service() -> Router {
|
||||
router
|
||||
}
|
||||
pub fn global_service() -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([http::Method::GET])
|
||||
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
|
||||
.allow_origin(Any);
|
||||
|
||||
Router::new()
|
||||
.route("/list_as_superadmin", get(list_workspaces_as_super_admin))
|
||||
.route("/list", get(list_workspaces))
|
||||
.route("/users", get(user_workspaces))
|
||||
.route("/users", get(user_workspaces).layer(cors.clone()))
|
||||
.route("/create", post(create_workspace))
|
||||
.route("/exists", post(exists_workspace))
|
||||
.route("/exists_username", post(exists_username))
|
||||
@@ -169,6 +177,7 @@ pub struct WorkspaceSettings {
|
||||
pub error_handler_muted_on_cancel: Option<bool>,
|
||||
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
|
||||
pub deploy_ui: Option<serde_json::Value>, // effectively: WorkspaceDeploymentUISettings
|
||||
pub default_app: Option<String>,
|
||||
pub automatic_billing: bool,
|
||||
pub default_scripts: Option<serde_json::Value>,
|
||||
@@ -1052,6 +1061,72 @@ async fn edit_git_sync_config(
|
||||
Ok(format!("Edit git sync config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditDeployUIConfig {
|
||||
deploy_ui_settings: Option<WorkspaceDeploymentUISettings>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
async fn edit_deploy_ui_config(
|
||||
_authed: ApiAuthed,
|
||||
Extension(_db): Extension<DB>,
|
||||
Path(_w_id): Path<String>,
|
||||
Json(_new_config): Json<EditDeployUIConfig>,
|
||||
) -> Result<String> {
|
||||
return Err(Error::BadRequest(
|
||||
"Deployment UI is only available on Windmill Enterprise Edition".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn edit_deploy_ui_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, .. }: ApiAuthed,
|
||||
Json(new_config): Json<EditDeployUIConfig>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let args_for_audit = format!("{:?}", new_config.deploy_ui_settings);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"workspaces.edit_deploy_ui_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("deployment_ui_settings", args_for_audit.as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(deploy_ui_settings) = new_config.deploy_ui_settings {
|
||||
let serialized_config =
|
||||
serde_json::to_value::<WorkspaceDeploymentUISettings>(deploy_ui_settings)
|
||||
.map_err(|err| Error::InternalErr(err.to_string()))?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2",
|
||||
serialized_config,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit deployment UI config for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditDefaultApp {
|
||||
pub default_app_path: Option<String>,
|
||||
@@ -2498,7 +2573,7 @@ async fn tarball_workspace(
|
||||
ScriptLang::Mssql => "ms.sql",
|
||||
ScriptLang::Graphql => "gql",
|
||||
ScriptLang::Nativets => "fetch.ts",
|
||||
ScriptLang::Bun => {
|
||||
ScriptLang::Bun | ScriptLang::Bunnative => {
|
||||
if default_ts.as_ref().is_some_and(|x| x == "bun") {
|
||||
"ts"
|
||||
} else {
|
||||
@@ -2599,9 +2674,9 @@ async fn tarball_workspace(
|
||||
if !skip_variables.unwrap_or(false) {
|
||||
let variables =
|
||||
sqlx::query_as::<_, ExportableListableVariable>(if !skip_secrets.unwrap_or(false) {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND path NOT LIKE 'u/%/secret_arg/%'"
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND expires_at IS NULL"
|
||||
} else {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND path NOT LIKE 'u/%/secret_arg/%'"
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND expires_at IS NULL"
|
||||
})
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -34,7 +34,11 @@ pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::d
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::DB) -> String {
|
||||
pub async fn renew_license_key(
|
||||
_http_client: &reqwest::Client,
|
||||
_db: &crate::db::DB,
|
||||
_key: Option<String>,
|
||||
) -> String {
|
||||
// Implementation is not open source
|
||||
"".to_string()
|
||||
}
|
||||
@@ -42,6 +46,7 @@ pub async fn renew_license_key(_http_client: &reqwest::Client, _db: &crate::db::
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn create_customer_portal_session(
|
||||
_http_client: &reqwest::Client,
|
||||
_key: Option<String>,
|
||||
) -> error::Result<String> {
|
||||
// Implementation is not open source
|
||||
Ok("".to_string())
|
||||
|
||||
@@ -38,22 +38,26 @@ pub struct Flow {
|
||||
pub dedicated_worker: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tag: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_false")]
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_false")]
|
||||
pub visible_to_runner_only: Option<bool>,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
b.is_none() || !b.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
pub struct ListableFlow {
|
||||
pub workspace_id: String,
|
||||
pub path: String,
|
||||
pub summary: String,
|
||||
pub description: String,
|
||||
pub edited_by: String,
|
||||
pub edited_at: chrono::DateTime<chrono::Utc>,
|
||||
pub edited_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub archived: bool,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub starred: bool,
|
||||
@@ -406,7 +410,7 @@ pub enum FlowModuleValue {
|
||||
lock: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(skip_serializing_if = "is_none_or_empty")]
|
||||
tag: Option<String>,
|
||||
language: ScriptLang,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -419,6 +423,10 @@ pub enum FlowModuleValue {
|
||||
Identity,
|
||||
}
|
||||
|
||||
fn is_none_or_empty(expr: &Option<String>) -> bool {
|
||||
expr.is_none() || expr.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UntaggedFlowModuleValue {
|
||||
#[serde(rename = "type")]
|
||||
|
||||
@@ -28,6 +28,7 @@ pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
|
||||
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
|
||||
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
|
||||
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";
|
||||
|
||||
pub const ENV_SETTINGS: [&str; 50] = [
|
||||
"DISABLE_NSJAIL",
|
||||
|
||||
@@ -222,11 +222,12 @@ pub async fn connect_db(
|
||||
DEFAULT_MAX_CONNECTIONS_INDEXER
|
||||
} else {
|
||||
DEFAULT_MAX_CONNECTIONS_WORKER
|
||||
* std::env::var("NUM_WORKERS")
|
||||
+ std::env::var("NUM_WORKERS")
|
||||
.ok()
|
||||
.map(|x| x.parse().ok())
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
- 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pub enum ScriptLang {
|
||||
Powershell,
|
||||
Postgresql,
|
||||
Bun,
|
||||
Bunnative,
|
||||
Mysql,
|
||||
Bigquery,
|
||||
Snowflake,
|
||||
@@ -46,6 +47,7 @@ impl ScriptLang {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ScriptLang::Bun => "bun",
|
||||
ScriptLang::Bunnative => "bunnative",
|
||||
ScriptLang::Nativets => "nativets",
|
||||
ScriptLang::Deno => "deno",
|
||||
ScriptLang::Python3 => "python3",
|
||||
@@ -131,6 +133,7 @@ impl Display for ScriptKind {
|
||||
}
|
||||
|
||||
pub const PREVIEW_IS_CODEBASE_HASH: i64 = -42;
|
||||
pub const PREVIEW_IS_TAR_CODEBASE_HASH: i64 = -43;
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
pub struct Script {
|
||||
@@ -148,6 +151,7 @@ pub struct Script {
|
||||
pub deleted: bool,
|
||||
pub is_template: bool,
|
||||
pub extra_perms: serde_json::Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lock: Option<String>,
|
||||
pub lock_error_logs: Option<String>,
|
||||
pub language: ScriptLang,
|
||||
|
||||
@@ -40,10 +40,10 @@ pub struct ListableVariable {
|
||||
pub is_refreshed: Option<bool>,
|
||||
pub refresh_error: Option<String>,
|
||||
pub is_linked: Option<bool>,
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
||||
pub struct ExportableListableVariable {
|
||||
pub workspace_id: String,
|
||||
pub path: String,
|
||||
@@ -51,8 +51,16 @@ pub struct ExportableListableVariable {
|
||||
pub is_secret: bool,
|
||||
pub description: String,
|
||||
pub extra_perms: serde_json::Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub account: Option<i32>,
|
||||
#[serde(skip_serializing_if = "is_none_or_false")]
|
||||
pub is_oauth: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
b.is_none() || !b.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -63,6 +71,7 @@ pub struct CreateVariable {
|
||||
pub description: String,
|
||||
pub account: Option<i32>,
|
||||
pub is_oauth: Option<bool>,
|
||||
pub expires_at: Option<chrono::DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub async fn build_crypt(db: &DB, w_id: &str) -> crate::error::Result<MagicCrypt256> {
|
||||
|
||||
@@ -57,6 +57,10 @@ lazy_static::lazy_static! {
|
||||
env_vars: Default::default(),
|
||||
}));
|
||||
|
||||
pub static ref WORKER_PULL_QUERIES: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(vec![]));
|
||||
pub static ref WORKER_SUSPENDED_PULL_QUERY: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
|
||||
|
||||
pub static ref SERVER_CONFIG: Arc<RwLock<ServerConfig>> = Arc::new(RwLock::new(ServerConfig { smtp: Default::default(), timeout_wait_result: 20 }));
|
||||
|
||||
|
||||
@@ -76,6 +80,63 @@ lazy_static::lazy_static! {
|
||||
|
||||
}
|
||||
|
||||
pub async fn make_suspended_pull_query(wc: &WorkerConfig) {
|
||||
let query = format!(
|
||||
"UPDATE queue
|
||||
SET running = true
|
||||
, started_at = coalesce(started_at, now())
|
||||
, last_ping = now()
|
||||
, suspend_until = null
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM queue
|
||||
WHERE suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now()) AND tag IN ({})
|
||||
ORDER BY priority DESC NULLS LAST, created_at
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
|
||||
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
|
||||
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
|
||||
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
|
||||
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
|
||||
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
|
||||
timeout, flow_step_id, cache_ttl, priority", wc.worker_tags.iter().map(|x| format!("'{x}'")).join(", "));
|
||||
let mut l = WORKER_SUSPENDED_PULL_QUERY.write().await;
|
||||
*l = query;
|
||||
}
|
||||
|
||||
pub async fn make_pull_query(wc: &WorkerConfig) {
|
||||
let mut queries = vec![];
|
||||
for tags in wc.priority_tags_sorted.iter() {
|
||||
let query = format!("UPDATE queue
|
||||
SET running = true
|
||||
, started_at = coalesce(started_at, now())
|
||||
, last_ping = now()
|
||||
, suspend_until = null
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM queue
|
||||
WHERE running = false AND tag IN ({}) AND scheduled_for <= now()
|
||||
ORDER BY priority DESC NULLS LAST, scheduled_for
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
|
||||
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
|
||||
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
|
||||
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
|
||||
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
|
||||
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
|
||||
timeout, flow_step_id, cache_ttl, priority", tags.tags.iter().map(|x| format!("'{x}'")).join(", "));
|
||||
|
||||
queries.push(query);
|
||||
}
|
||||
|
||||
let mut l = WORKER_PULL_QUERIES.write().await;
|
||||
*l = queries;
|
||||
}
|
||||
|
||||
pub const TMP_DIR: &str = "/tmp/windmill";
|
||||
|
||||
pub async fn reload_custom_tags_setting(db: &DB) -> error::Result<()> {
|
||||
@@ -156,6 +217,8 @@ fn parse_file<T: FromStr>(path: &str) -> Option<T> {
|
||||
pub struct Annotations {
|
||||
pub npm_mode: bool,
|
||||
pub nodejs_mode: bool,
|
||||
pub native_mode: bool,
|
||||
pub nobundling: bool,
|
||||
}
|
||||
|
||||
pub fn get_annotation(inner_content: &str) -> Annotations {
|
||||
@@ -166,8 +229,12 @@ pub fn get_annotation(inner_content: &str) -> Annotations {
|
||||
.collect_vec();
|
||||
let nodejs_mode: bool = annotations.contains(&"nodejs".to_string());
|
||||
let npm_mode: bool = annotations.contains(&"npm".to_string());
|
||||
let native_mode: bool = annotations.contains(&"native".to_string());
|
||||
|
||||
Annotations { npm_mode, nodejs_mode }
|
||||
//TODO: remove || npm_mode when bun build is more powerful
|
||||
let nobundling: bool = annotations.contains(&"nobundling".to_string()) || nodejs_mode;
|
||||
|
||||
Annotations { npm_mode, nodejs_mode, native_mode, nobundling }
|
||||
}
|
||||
|
||||
pub async fn load_cache(bin_path: &str, _remote_path: &str) -> (bool, String) {
|
||||
|
||||
@@ -7,6 +7,12 @@ pub struct WorkspaceGitSyncSettings {
|
||||
pub repositories: Vec<GitRepositorySettings>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct WorkspaceDeploymentUISettings {
|
||||
pub include_path: Vec<String>,
|
||||
pub include_type: Vec<ObjectType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
pub enum ObjectType {
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
vec,
|
||||
};
|
||||
use std::{borrow::Borrow, collections::HashMap, sync::Arc, vec};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_recursion::async_recursion;
|
||||
@@ -65,6 +60,7 @@ use windmill_common::{
|
||||
utils::{not_found_if_none, report_critical_error, StripPath},
|
||||
worker::{
|
||||
to_raw_value, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, NO_LOGS, WORKER_CONFIG,
|
||||
WORKER_PULL_QUERIES, WORKER_SUSPENDED_PULL_QUERY,
|
||||
},
|
||||
BASE_URL, DB, METRICS_ENABLED,
|
||||
};
|
||||
@@ -1761,8 +1757,7 @@ pub async fn pull<R: rsmq_async::RsmqConnection + Send + Clone>(
|
||||
let min_started_at = sqlx::query!(
|
||||
"SELECT COALESCE((SELECT MIN(started_at) as min_started_at
|
||||
FROM queue
|
||||
WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false
|
||||
GROUP BY script_path), $3) as min_started_at, now() AS now",
|
||||
WHERE script_path = $1 AND job_kind != 'dependencies' AND running = true AND workspace_id = $2 AND canceled = false AND concurrent_limit > 0), $3) as min_started_at, now() AS now",
|
||||
job_script_path,
|
||||
&pulled_job.workspace_id,
|
||||
completed_count.max_ended_at
|
||||
@@ -1985,35 +1980,11 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
|
||||
* suspend_until is non-null
|
||||
* and suspend = 0 when the resume messages are received
|
||||
* or suspend_until <= now() if it has timed out */
|
||||
let config = WORKER_CONFIG.read().await.clone();
|
||||
let tags = config.worker_tags.clone();
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let priority_tags_sorted = vec![PriorityTags { priority: 0, tags: tags.clone() }];
|
||||
#[cfg(feature = "enterprise")]
|
||||
let priority_tags_sorted = config.priority_tags_sorted.clone();
|
||||
drop(config);
|
||||
let query = WORKER_SUSPENDED_PULL_QUERY.read().await;
|
||||
|
||||
let r = if suspend_first {
|
||||
sqlx::query_as::<_, QueuedJob>("UPDATE queue
|
||||
SET running = true
|
||||
, started_at = coalesce(started_at, now())
|
||||
, last_ping = now()
|
||||
, suspend_until = null
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM queue
|
||||
WHERE suspend_until IS NOT NULL AND (suspend <= 0 OR suspend_until <= now()) AND tag = ANY($1)
|
||||
ORDER BY priority DESC NULLS LAST, created_at
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
|
||||
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
|
||||
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
|
||||
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
|
||||
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
|
||||
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
|
||||
timeout, flow_step_id, cache_ttl, priority")
|
||||
.bind(tags)
|
||||
// tracing::info!("Pulling job with query: {}", query);
|
||||
sqlx::query_as::<_, QueuedJob>(&query)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
} else {
|
||||
@@ -2024,41 +1995,16 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
|
||||
// let instant = Instant::now();
|
||||
let mut highest_priority_job: Option<QueuedJob> = None;
|
||||
|
||||
for priority_tags in priority_tags_sorted {
|
||||
let r = sqlx::query_as::<_, QueuedJob>(
|
||||
"UPDATE queue
|
||||
SET running = true
|
||||
, started_at = coalesce(started_at, now())
|
||||
, last_ping = now()
|
||||
, suspend_until = null
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM queue
|
||||
WHERE running = false AND scheduled_for <= now() AND tag = ANY($1)
|
||||
ORDER BY priority DESC NULLS LAST, scheduled_for, created_at
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id, workspace_id, parent_job, created_by, created_at, started_at, scheduled_for,
|
||||
running, script_hash, script_path, args, null as logs, raw_code, canceled, canceled_by,
|
||||
canceled_reason, last_ping, job_kind, schedule_path, permissioned_as,
|
||||
flow_status, raw_flow, is_flow_step, language, suspend, suspend_until,
|
||||
same_worker, raw_lock, pre_run_error, email, visible_to_owner, mem_peak,
|
||||
root_job, leaf_jobs, tag, concurrent_limit, concurrency_time_window_s,
|
||||
timeout, flow_step_id, cache_ttl, priority",
|
||||
)
|
||||
.bind(priority_tags.tags.clone())
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
let queries = WORKER_PULL_QUERIES.read().await;
|
||||
|
||||
for query in queries.iter() {
|
||||
// tracing::info!("Pulling job with query: {}", query);
|
||||
let r = sqlx::query_as::<_, QueuedJob>(query)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
if let Some(pulled_job) = r {
|
||||
let id = pulled_job.id;
|
||||
highest_priority_job = Some(pulled_job);
|
||||
tracing::debug!(
|
||||
"Pulling for job {id} with tags {:?} with priority {}",
|
||||
priority_tags.tags,
|
||||
priority_tags.priority
|
||||
);
|
||||
break;
|
||||
}
|
||||
// else continue pulling for lower priority tags
|
||||
@@ -2199,35 +2145,51 @@ pub async fn get_result_by_id(
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(_) => {
|
||||
let running_flow_job = sqlx::query_as::<_, QueuedJob>(
|
||||
let running_flow_job =sqlx::query_as::<_, QueuedJob>(
|
||||
"SELECT * FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $1), $1) = id AND workspace_id = $2"
|
||||
).bind(flow_id)
|
||||
.bind(&w_id)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
.fetch_optional(&db).await?;
|
||||
match running_flow_job {
|
||||
Some(job) => {
|
||||
let restarted_from = windmill_common::utils::not_found_if_none(
|
||||
job.parse_flow_status()
|
||||
.map(|status| status.restarted_from)
|
||||
.flatten(),
|
||||
"Flow result by id in leaf jobs",
|
||||
format!("{}, {}", flow_id, node_id),
|
||||
)?;
|
||||
|
||||
let restarted_from = windmill_common::utils::not_found_if_none(
|
||||
running_flow_job
|
||||
.map(|fj| fj.parse_flow_status())
|
||||
.flatten()
|
||||
.map(|status| status.restarted_from)
|
||||
.flatten(),
|
||||
"Flow result by id in leaf jobs",
|
||||
format!("{}, {}", flow_id, node_id),
|
||||
)?;
|
||||
|
||||
get_result_by_id_from_original_flow(
|
||||
&db,
|
||||
w_id.as_str(),
|
||||
&restarted_from.flow_job_id,
|
||||
node_id.as_str(),
|
||||
json_path.clone(),
|
||||
)
|
||||
.await
|
||||
get_result_by_id_from_original_flow(
|
||||
&db,
|
||||
w_id.as_str(),
|
||||
&restarted_from.flow_job_id,
|
||||
node_id.as_str(),
|
||||
json_path.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
get_result_by_id_from_original_flow(
|
||||
&db,
|
||||
w_id.as_str(),
|
||||
&flow_id,
|
||||
node_id.as_str(),
|
||||
json_path.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
struct FlowJobResult {
|
||||
leaf_jobs: Option<Json<Box<RawValue>>>,
|
||||
parent_job: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
pub async fn get_result_by_id_from_running_flow(
|
||||
db: &Pool<Postgres>,
|
||||
@@ -2236,12 +2198,11 @@ pub async fn get_result_by_id_from_running_flow(
|
||||
node_id: &str,
|
||||
json_path: Option<String>,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
let flow_job_result = sqlx::query!(
|
||||
"SELECT leaf_jobs->$1::text as leaf_jobs, parent_job FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3",
|
||||
node_id,
|
||||
flow_id,
|
||||
w_id,
|
||||
)
|
||||
let flow_job_result = sqlx::query_as::<_, FlowJobResult>(
|
||||
"SELECT leaf_jobs->$1::text as leaf_jobs, parent_job FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3")
|
||||
.bind(node_id)
|
||||
.bind(flow_id)
|
||||
.bind(w_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
@@ -2253,7 +2214,7 @@ pub async fn get_result_by_id_from_running_flow(
|
||||
|
||||
let job_result = flow_job_result
|
||||
.leaf_jobs
|
||||
.map(|x| serde_json::from_value(x).ok())
|
||||
.map(|x| serde_json::from_str(x.get()).ok())
|
||||
.flatten();
|
||||
|
||||
if job_result.is_none() && flow_job_result.parent_job.is_some() {
|
||||
@@ -2276,6 +2237,61 @@ pub async fn get_result_by_id_from_running_flow(
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn get_completed_flow_node_result_rec(
|
||||
db: &Pool<Postgres>,
|
||||
w_id: &str,
|
||||
subflows: Vec<CompletedJob>,
|
||||
node_id: &str,
|
||||
json_path: Option<String>,
|
||||
) -> error::Result<Option<Box<RawValue>>> {
|
||||
for subflow in subflows {
|
||||
let flow_status = subflow.parse_flow_status().ok_or_else(|| {
|
||||
error::Error::InternalErr(format!("Could not parse flow status of {}", subflow.id))
|
||||
})?;
|
||||
|
||||
if let Some(node_status) = flow_status
|
||||
.modules
|
||||
.iter()
|
||||
.find(|module| module.id() == node_id)
|
||||
{
|
||||
return match (node_status.job(), node_status.flow_jobs()) {
|
||||
(Some(leaf_job_uuid), None) => extract_result_from_job_result(
|
||||
db,
|
||||
w_id,
|
||||
JobResult::SingleJob(leaf_job_uuid),
|
||||
json_path.clone(),
|
||||
)
|
||||
.await
|
||||
.map(Some),
|
||||
(Some(_), Some(jobs)) => extract_result_from_job_result(
|
||||
db,
|
||||
w_id,
|
||||
JobResult::ListJob(jobs),
|
||||
json_path.clone(),
|
||||
)
|
||||
.await
|
||||
.map(Some),
|
||||
_ => Err(error::Error::NotFound(format!(
|
||||
"Flow result by id in leaf jobs not found at name {}",
|
||||
node_id,
|
||||
))),
|
||||
};
|
||||
} else {
|
||||
let subflows = sqlx::query_as::<_, CompletedJob>(
|
||||
"SELECT *, null as labels FROM completed_job WHERE parent_job = $1 AND workspace_id = $2 AND flow_status IS NOT NULL",
|
||||
).bind(subflow.id).bind(w_id).fetch_all(db).await?;
|
||||
match get_completed_flow_node_result_rec(db, w_id, subflows, node_id, json_path.clone())
|
||||
.await?
|
||||
{
|
||||
Some(res) => return Ok(Some(res)),
|
||||
None => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn get_result_by_id_from_original_flow(
|
||||
db: &Pool<Postgres>,
|
||||
w_id: &str,
|
||||
@@ -2294,130 +2310,16 @@ async fn get_result_by_id_from_original_flow(
|
||||
let flow_job = windmill_common::utils::not_found_if_none(
|
||||
flow_job,
|
||||
"Flow result by id in leaf jobs",
|
||||
format!("{}", completed_flow_id),
|
||||
format!("{}, {}", completed_flow_id, node_id),
|
||||
)?;
|
||||
|
||||
let mut leaf_jobs_for_flow = HashMap::<String, JobResult>::new();
|
||||
compute_leaf_jobs_for_completed_flow(
|
||||
db,
|
||||
w_id,
|
||||
flow_job.id,
|
||||
Some(flow_job.clone()),
|
||||
&mut leaf_jobs_for_flow,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !leaf_jobs_for_flow.contains_key(&node_id.to_string()) {
|
||||
// if the flow is itself a restart flow, the step job might be from the upstream flow
|
||||
let restarted_from = windmill_common::utils::not_found_if_none(
|
||||
flow_job
|
||||
.parse_flow_status()
|
||||
.map(|status| status.restarted_from)
|
||||
.flatten(),
|
||||
"Flow result by id in leaf jobs",
|
||||
format!("{}", completed_flow_id),
|
||||
)?;
|
||||
return get_result_by_id_from_original_flow(
|
||||
db,
|
||||
w_id,
|
||||
&restarted_from.flow_job_id,
|
||||
node_id,
|
||||
json_path,
|
||||
)
|
||||
.await;
|
||||
match get_completed_flow_node_result_rec(db, w_id, vec![flow_job], node_id, json_path).await? {
|
||||
Some(res) => Ok(res),
|
||||
None => Err(error::Error::NotFound(format!(
|
||||
"Flow result by id in leaf jobs not found at name {}, {}",
|
||||
completed_flow_id, node_id
|
||||
))),
|
||||
}
|
||||
|
||||
// if the job is in the leaf_jobs map, then fetch its result and return
|
||||
let leaf_job_uuid = leaf_jobs_for_flow.get(&node_id.to_string()).unwrap();
|
||||
extract_result_from_job_result(db, w_id, leaf_job_uuid.to_owned(), json_path).await
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn compute_leaf_jobs_for_completed_flow(
|
||||
db: &Pool<Postgres>,
|
||||
w_id: &str,
|
||||
completed_flow_id: Uuid,
|
||||
completed_flow_row: Option<CompletedJob>, // if provided, will be used, otherwise completed_flow_id must be set and the job definition will be pulled from DB
|
||||
recursive_result: &mut HashMap<String, JobResult>,
|
||||
) -> error::Result<()> {
|
||||
let flow_status = match completed_flow_row {
|
||||
Some(job) => job.parse_flow_status(),
|
||||
None => {
|
||||
let job_status_raw = sqlx::query_scalar!(
|
||||
"SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
completed_flow_id,
|
||||
w_id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
job_status_raw
|
||||
.map(|raw| serde_json::from_value::<FlowStatus>(raw).ok())
|
||||
.flatten()
|
||||
}
|
||||
};
|
||||
|
||||
let flow_job_status_modules = windmill_common::utils::not_found_if_none(
|
||||
flow_status.map(|fs| fs.modules),
|
||||
"Flow result by id in leaf jobs",
|
||||
format!("{}", completed_flow_id),
|
||||
)?;
|
||||
|
||||
let children_jobs = sqlx::query_as::<_, (Uuid, JobKind)>(
|
||||
"SELECT id, job_kind FROM completed_job WHERE parent_job = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(completed_flow_id)
|
||||
.bind(w_id)
|
||||
.fetch_all(db)
|
||||
.await?;
|
||||
|
||||
for child_job in children_jobs {
|
||||
let child_job_id = child_job.0;
|
||||
let child_job_kind = child_job.1;
|
||||
match child_job_kind {
|
||||
JobKind::Script | JobKind::Preview | JobKind::Script_Hub => {
|
||||
// if is potentially a leaf job. Get its step_id from the initial flow definition and add it to the result map
|
||||
for module in &flow_job_status_modules {
|
||||
if module.job().map(|id| id == child_job_id).unwrap_or(false) {
|
||||
recursive_result.insert(module.id(), JobResult::SingleJob(child_job_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
JobKind::Flow | JobKind::FlowPreview | JobKind::SingleScriptFlow => {
|
||||
// Extract the leaf job for this flow and add them to the result map
|
||||
for module in &flow_job_status_modules {
|
||||
// we add the module as an element of ListJob for this step ID and recursiively extract leaf job of the sub-flow
|
||||
match module {
|
||||
FlowStatusModule::Success { flow_jobs: Some(jobs), .. } => {
|
||||
let jobs_set: HashSet<Uuid> = HashSet::from_iter(jobs.iter().cloned());
|
||||
if jobs_set.contains(&child_job_id) {
|
||||
let new_list_job = match recursive_result.get(&module.id()) {
|
||||
Some(JobResult::ListJob(jobs_list)) => {
|
||||
let mut jobs_list_c = jobs_list.clone();
|
||||
jobs_list_c.push(child_job_id);
|
||||
jobs_list_c
|
||||
}
|
||||
_ => vec![child_job_id],
|
||||
};
|
||||
recursive_result
|
||||
.insert(module.id(), JobResult::ListJob(new_list_job));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
compute_leaf_jobs_for_completed_flow(
|
||||
db,
|
||||
w_id,
|
||||
child_job_id,
|
||||
None,
|
||||
recursive_result,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => {} // do nothing
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn extract_result_from_job_result(
|
||||
@@ -2427,27 +2329,52 @@ async fn extract_result_from_job_result(
|
||||
json_path: Option<String>,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
match job_result {
|
||||
JobResult::ListJob(job_ids) => {
|
||||
let rows = sqlx::query_as::<_, ResultWithId>(
|
||||
"SELECT id, result FROM completed_job WHERE id = ANY($1) AND workspace_id = $2",
|
||||
)
|
||||
.bind(job_ids.as_slice())
|
||||
.bind(w_id)
|
||||
.fetch_all(db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|x| x.result.map(|y| (x.id, y)))
|
||||
.collect::<HashMap<Uuid, Json<Box<RawValue>>>>();
|
||||
let result = job_ids
|
||||
JobResult::ListJob(job_ids) => match json_path {
|
||||
Some(json_path) => {
|
||||
let mut parts = json_path.split(".");
|
||||
|
||||
let Some(idx) = parts.next().map(|x| x.parse::<usize>().ok()).flatten() else {
|
||||
return Ok(to_raw_value(&serde_json::Value::Null));
|
||||
};
|
||||
let Some(job_id) = job_ids.get(idx).cloned() else {
|
||||
return Ok(to_raw_value(&serde_json::Value::Null));
|
||||
};
|
||||
Ok(sqlx::query_as::<_, ResultR>(
|
||||
"SELECT result #> $3 as result FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(job_id)
|
||||
.bind(w_id)
|
||||
.bind(
|
||||
parts.map(|x| x.to_string()).collect::<Vec<_>>()
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.map(|r| r.result.map(|x| x.0))
|
||||
.flatten()
|
||||
.unwrap_or_else(|| to_raw_value(&serde_json::Value::Null)))
|
||||
}
|
||||
None => {
|
||||
let rows = sqlx::query_as::<_, ResultWithId>(
|
||||
"SELECT id, result FROM completed_job WHERE id = ANY($1) AND workspace_id = $2",
|
||||
)
|
||||
.bind(job_ids.as_slice())
|
||||
.bind(w_id)
|
||||
.fetch_all(db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
rows.get(&id)
|
||||
.map(|x| x.0.clone())
|
||||
.unwrap_or_else(|| to_raw_value(&serde_json::Value::Null))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(to_raw_value(&result))
|
||||
}
|
||||
.filter_map(|x| x.result.map(|y| (x.id, y)))
|
||||
.collect::<HashMap<Uuid, Json<Box<RawValue>>>>();
|
||||
let result = job_ids
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
rows.get(&id)
|
||||
.map(|x| x.0.clone())
|
||||
.unwrap_or_else(|| to_raw_value(&serde_json::Value::Null))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(to_raw_value(&result))
|
||||
}
|
||||
},
|
||||
JobResult::SingleJob(x) => Ok(sqlx::query_as::<_, ResultR>(
|
||||
"SELECT result #> $3 as result FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
)
|
||||
@@ -2863,6 +2790,14 @@ where
|
||||
Error::BadRequest(format!("Cloud events batching is not supported yet"))
|
||||
.into_response(),
|
||||
)
|
||||
} else if content_type.unwrap().starts_with("text/plain") {
|
||||
let bytes = Bytes::from_request(req, _state)
|
||||
.await
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
let str = String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)).into_response())?;
|
||||
extra.insert("raw_string".to_string(), to_raw_value(&str));
|
||||
Ok(PushArgsOwned { extra: Some(extra), args: HashMap::new() })
|
||||
} else if content_type
|
||||
.unwrap()
|
||||
.starts_with("application/x-www-form-urlencoded")
|
||||
@@ -3695,10 +3630,15 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
language
|
||||
.as_ref()
|
||||
.map(|x| {
|
||||
if per_workspace {
|
||||
format!("{}-{}", x.as_str(), workspace_id)
|
||||
let tag_lang = if x == &ScriptLang::Bunnative {
|
||||
ScriptLang::Nativets.as_str()
|
||||
} else {
|
||||
x.as_str().to_string()
|
||||
x.as_str()
|
||||
};
|
||||
if per_workspace {
|
||||
format!("{}-{}", tag_lang, workspace_id)
|
||||
} else {
|
||||
tag_lang.to_string()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(default)
|
||||
|
||||
@@ -14,6 +14,7 @@ use sqlx::{query_scalar, Postgres, Transaction};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use windmill_common::db::Authed;
|
||||
use windmill_common::ee::LICENSE_KEY_VALID;
|
||||
use windmill_common::flows::Retry;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::schedule::schedule_to_user;
|
||||
@@ -31,6 +32,13 @@ pub async fn push_scheduled_job<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
schedule: &Schedule,
|
||||
authed: Option<&Authed>,
|
||||
) -> Result<QueueTransaction<'c, R>> {
|
||||
if !*LICENSE_KEY_VALID.read().await {
|
||||
return Err(error::Error::BadRequest(
|
||||
"License key is not valid. Go to your superadmin settings to update your license key."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sched = cron::Schedule::from_str(schedule.schedule.as_ref())
|
||||
.map_err(|e| error::Error::BadRequest(e.to_string()))?;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ flamegraph = []
|
||||
parquet = ["windmill-common/parquet", "dep:object_store"]
|
||||
flow_testing = []
|
||||
cloud = []
|
||||
sqlx = []
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, fs, io, path::Path, process::Stdio};
|
||||
use std::{collections::HashMap, fs, io, path::Path, process::Stdio, time::Instant};
|
||||
|
||||
use base64::Engine;
|
||||
use itertools::Itertools;
|
||||
@@ -34,7 +34,9 @@ use windmill_common::variables;
|
||||
|
||||
use windmill_common::{
|
||||
error::{self, Result},
|
||||
get_latest_hash_for_path,
|
||||
jobs::QueuedJob,
|
||||
scripts::ScriptLang,
|
||||
worker::{exists_in_cache, get_annotation, save_cache},
|
||||
DB,
|
||||
};
|
||||
@@ -71,6 +73,8 @@ pub async fn gen_lockfile(
|
||||
let common_bun_proc_envs: HashMap<String, String> =
|
||||
get_common_bun_proc_envs(&base_internal_url).await;
|
||||
|
||||
let mut empty_deps = false;
|
||||
|
||||
if let Some(raw_deps) = raw_deps {
|
||||
gen_bunfig(job_dir).await?;
|
||||
write_file(job_dir, "package.json", raw_deps.as_str()).await?;
|
||||
@@ -123,20 +127,30 @@ pub async fn gen_lockfile(
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let new_package_json = read_file_content(&format!("{job_dir}/package.json")).await?;
|
||||
empty_deps = new_package_json
|
||||
== r#"{
|
||||
"dependencies": {}
|
||||
}"#;
|
||||
}
|
||||
|
||||
install_lockfile(
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_id,
|
||||
w_id,
|
||||
db,
|
||||
job_dir,
|
||||
worker_name,
|
||||
common_bun_proc_envs,
|
||||
npm_mode,
|
||||
)
|
||||
.await?;
|
||||
if !empty_deps {
|
||||
install_lockfile(
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_id,
|
||||
w_id,
|
||||
db,
|
||||
job_dir,
|
||||
worker_name,
|
||||
common_bun_proc_envs,
|
||||
npm_mode,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
append_logs(job_id, w_id, "\nempty dependencies, skipping install", db).await;
|
||||
}
|
||||
|
||||
if export_pkg {
|
||||
let mut content = "".to_string();
|
||||
@@ -148,7 +162,7 @@ pub async fn gen_lockfile(
|
||||
content.push_str(BUN_LOCKB_SPLIT);
|
||||
{
|
||||
let file = format!("{job_dir}/bun.lockb");
|
||||
if tokio::fs::metadata(&file).await.is_ok() {
|
||||
if !empty_deps && tokio::fs::metadata(&file).await.is_ok() {
|
||||
let mut file = File::open(&file).await?;
|
||||
let mut buf = vec![];
|
||||
file.read_to_end(&mut buf).await?;
|
||||
@@ -290,6 +304,7 @@ enum LoaderMode {
|
||||
Bun,
|
||||
BunBundle,
|
||||
NodeBundle,
|
||||
BrowserBundle,
|
||||
}
|
||||
async fn build_loader(
|
||||
job_dir: &str,
|
||||
@@ -359,7 +374,10 @@ plugin(p)
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
} else if mode == LoaderMode::BunBundle || mode == LoaderMode::NodeBundle {
|
||||
} else if mode == LoaderMode::BunBundle
|
||||
|| mode == LoaderMode::NodeBundle
|
||||
|| mode == LoaderMode::BrowserBundle
|
||||
{
|
||||
write_file(
|
||||
&job_dir,
|
||||
"node_builder.ts",
|
||||
@@ -372,7 +390,7 @@ const bo = await Bun.build({{
|
||||
outdir: "./",
|
||||
target: "{}",
|
||||
plugins: [p],
|
||||
external: [],
|
||||
external: ["electron"],
|
||||
minify: {{
|
||||
identifiers: false,
|
||||
syntax: true,
|
||||
@@ -388,8 +406,10 @@ if (!bo.success) {{
|
||||
loader,
|
||||
if mode == LoaderMode::BunBundle {
|
||||
"bun"
|
||||
} else {
|
||||
} else if mode == LoaderMode::NodeBundle {
|
||||
"node"
|
||||
} else {
|
||||
"browser"
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -481,24 +501,36 @@ pub async fn generate_bun_bundle(
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<()> {
|
||||
use crate::global_cache::extract_tar;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
let bun_cache_path = format!("{}/{}", BUN_CACHE_DIR, path);
|
||||
let dst = format!("{job_dir}/main.js");
|
||||
let bun_cache_path = format!("{}/{}", crate::ROOT_CACHE_NOMOUNT_DIR, path);
|
||||
let is_tar = id.ends_with(".tar");
|
||||
|
||||
let dst = format!(
|
||||
"{job_dir}/{}",
|
||||
if is_tar { "codebase.tar" } else { "main.js" }
|
||||
);
|
||||
let dirs_splitted = bun_cache_path.split("/").collect_vec();
|
||||
tokio::fs::create_dir_all(dirs_splitted[..dirs_splitted.len() - 1].join("/")).await?;
|
||||
if tokio::fs::metadata(&bun_cache_path).await.is_ok() {
|
||||
tracing::info!("loading {bun_cache_path} from cache");
|
||||
tokio::fs::symlink(&bun_cache_path, dst).await?;
|
||||
if is_tar {
|
||||
extract_tar(fs::read(bun_cache_path)?.into(), job_dir).await?;
|
||||
} else {
|
||||
tokio::fs::symlink(&bun_cache_path, dst).await?;
|
||||
}
|
||||
} else if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
{
|
||||
let bytes = attempt_fetch_bytes(os, &path).await?;
|
||||
if *windmill_common::worker::CLOUD_HOSTED {
|
||||
tokio::fs::write(dst, &bytes).await?;
|
||||
|
||||
tokio::fs::write(&bun_cache_path, &bytes).await?;
|
||||
if is_tar {
|
||||
extract_tar(bytes, job_dir).await?;
|
||||
} else {
|
||||
tokio::fs::write(&bun_cache_path, &bytes).await?;
|
||||
tokio::fs::symlink(bun_cache_path, dst).await?;
|
||||
}
|
||||
|
||||
@@ -565,11 +597,15 @@ pub async fn prebundle_script(
|
||||
worker_name: &str,
|
||||
token: &str,
|
||||
) -> Result<()> {
|
||||
let (local_path, remote_path) = compute_bundle_local_and_remote_path(inner_content, &lockfile);
|
||||
let (local_path, remote_path) =
|
||||
compute_bundle_local_and_remote_path(inner_content, &lockfile, script_path, db, w_id).await;
|
||||
if exists_in_cache(&local_path, &remote_path).await {
|
||||
return Ok(());
|
||||
}
|
||||
let annotation = get_annotation(inner_content);
|
||||
if annotation.nobundling {
|
||||
return Ok(());
|
||||
}
|
||||
let origin = format!("{job_dir}/main.js");
|
||||
write_file(job_dir, "main.ts", &remove_pinned_imports(inner_content)?).await?;
|
||||
build_loader(
|
||||
@@ -580,6 +616,8 @@ pub async fn prebundle_script(
|
||||
script_path,
|
||||
if annotation.nodejs_mode {
|
||||
LoaderMode::NodeBundle
|
||||
} else if annotation.native_mode {
|
||||
LoaderMode::BrowserBundle
|
||||
} else {
|
||||
LoaderMode::BunBundle
|
||||
},
|
||||
@@ -607,18 +645,48 @@ pub async fn prebundle_script(
|
||||
|
||||
pub const BUN_BUNDLE_OBJECT_STORE_PREFIX: &str = "bun_bundle/";
|
||||
|
||||
fn compute_bundle_local_and_remote_path(
|
||||
async fn get_script_import_updated_at(db: &DB, w_id: &str, script_path: &str) -> Result<String> {
|
||||
let script_hash = get_latest_hash_for_path(&mut db.begin().await?, w_id, script_path).await?;
|
||||
let last_updated_at = sqlx::query_scalar!(
|
||||
"SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2",
|
||||
w_id,
|
||||
script_hash.0 .0
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
Ok(last_updated_at.to_string())
|
||||
}
|
||||
|
||||
async fn compute_bundle_local_and_remote_path(
|
||||
inner_content: &str,
|
||||
requirements_o: &Option<String>,
|
||||
script_path: &str,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> (String, String) {
|
||||
let hash = windmill_common::utils::calculate_hash(&format!(
|
||||
let mut input_src = format!(
|
||||
"{}{}",
|
||||
inner_content,
|
||||
requirements_o
|
||||
.as_ref()
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default()
|
||||
));
|
||||
);
|
||||
|
||||
let relative_imports = crate::worker_lockfiles::extract_relative_imports(
|
||||
&inner_content,
|
||||
script_path,
|
||||
&Some(ScriptLang::Bun),
|
||||
);
|
||||
|
||||
for path in relative_imports.unwrap_or_default() {
|
||||
if let Ok(updated_at) = get_script_import_updated_at(db, w_id, &path).await {
|
||||
input_src.push_str(&path);
|
||||
input_src.push_str(&updated_at.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let hash = windmill_common::utils::calculate_hash(&input_src);
|
||||
let local_path = format!("{BUN_BUNDLE_CACHE_DIR}/{hash}");
|
||||
let remote_path = format!("{BUN_BUNDLE_OBJECT_STORE_PREFIX}{hash}");
|
||||
(local_path, remote_path)
|
||||
@@ -640,28 +708,35 @@ pub async fn handle_bun_job(
|
||||
envs: HashMap<String, String>,
|
||||
shared_mount: &str,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
let (mut bundle_cache, cache_logs, local_path, remote_path) = if requirements_o.is_some()
|
||||
&& codebase.is_none()
|
||||
{
|
||||
let (local_path, remote_path) =
|
||||
compute_bundle_local_and_remote_path(inner_content, &requirements_o);
|
||||
let (cache, logs) = windmill_common::worker::load_cache(&local_path, &remote_path).await;
|
||||
(cache, logs, local_path, remote_path)
|
||||
} else {
|
||||
(false, "".to_string(), "".to_string(), "".to_string())
|
||||
};
|
||||
let mut annotation = windmill_common::worker::get_annotation(inner_content);
|
||||
|
||||
if !codebase.is_some() && !bundle_cache {
|
||||
let (mut has_bundle_cache, cache_logs, local_path, remote_path) =
|
||||
if requirements_o.is_some() && !annotation.nobundling && codebase.is_none() {
|
||||
let (local_path, remote_path) = compute_bundle_local_and_remote_path(
|
||||
inner_content,
|
||||
&requirements_o,
|
||||
job.script_path(),
|
||||
db,
|
||||
&job.workspace_id,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (cache, logs) =
|
||||
windmill_common::worker::load_cache(&local_path, &remote_path).await;
|
||||
(cache, logs, local_path, remote_path)
|
||||
} else {
|
||||
(false, "".to_string(), "".to_string(), "".to_string())
|
||||
};
|
||||
|
||||
if !codebase.is_some() && !has_bundle_cache {
|
||||
let _ = write_file(job_dir, "main.ts", inner_content).await?;
|
||||
} else {
|
||||
} else if !annotation.native_mode && codebase.is_none() {
|
||||
let _ = write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?;
|
||||
};
|
||||
|
||||
let common_bun_proc_envs: HashMap<String, String> =
|
||||
get_common_bun_proc_envs(&base_internal_url).await;
|
||||
|
||||
let mut annotation = windmill_common::worker::get_annotation(inner_content);
|
||||
|
||||
if codebase.is_some() {
|
||||
annotation.nodejs_mode = true
|
||||
}
|
||||
@@ -675,7 +750,7 @@ pub async fn handle_bun_job(
|
||||
}
|
||||
|
||||
let mut gbuntar_name = None;
|
||||
if bundle_cache {
|
||||
if has_bundle_cache {
|
||||
let target = format!("{job_dir}/main.js");
|
||||
std::os::unix::fs::symlink(&local_path, &target).map_err(|e| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
@@ -790,7 +865,9 @@ pub async fn handle_bun_job(
|
||||
// }
|
||||
}
|
||||
|
||||
let mut init_logs = if bundle_cache {
|
||||
let mut init_logs = if annotation.native_mode {
|
||||
"\n\n--- NATIVE CODE EXECUTION ---\n".to_string()
|
||||
} else if has_bundle_cache {
|
||||
if annotation.nodejs_mode {
|
||||
"\n\n--- NODE BUNDLE SNAPSHOT EXECUTION ---\n".to_string()
|
||||
} else {
|
||||
@@ -798,6 +875,8 @@ pub async fn handle_bun_job(
|
||||
}
|
||||
} else if codebase.is_some() {
|
||||
"\n\n--- NODE CODEBASE SNAPSHOT EXECUTION ---\n".to_string()
|
||||
} else if annotation.native_mode {
|
||||
"\n\n--- NATIVE CODE EXECUTION ---\n".to_string()
|
||||
} else if annotation.nodejs_mode {
|
||||
write_file(job_dir, "main.ts", &remove_pinned_imports(inner_content)?).await?;
|
||||
"\n\n--- NODE CODE EXECUTION ---\n".to_string()
|
||||
@@ -813,11 +892,14 @@ pub async fn handle_bun_job(
|
||||
);
|
||||
}
|
||||
|
||||
if bundle_cache {
|
||||
if has_bundle_cache {
|
||||
init_logs = format!("\n{}{}", cache_logs, init_logs);
|
||||
}
|
||||
|
||||
let write_wrapper_f = async {
|
||||
if !has_bundle_cache && annotation.native_mode {
|
||||
return Ok(()) as error::Result<()>;
|
||||
}
|
||||
// let mut start = Instant::now();
|
||||
let args =
|
||||
windmill_parser_ts::parse_deno_signature(inner_content, true, main_override.clone())?
|
||||
@@ -840,7 +922,7 @@ pub async fn handle_bun_job(
|
||||
// we cannot use Bun.read and Bun.write because it results in an EBADF error on cloud
|
||||
let main_name = main_override.unwrap_or("main".to_string());
|
||||
|
||||
let main_import = if codebase.is_some() || bundle_cache {
|
||||
let main_import = if codebase.is_some() || has_bundle_cache {
|
||||
"./main.js"
|
||||
} else {
|
||||
"./main.ts"
|
||||
@@ -886,7 +968,9 @@ try {{
|
||||
|
||||
let reserved_variables_args_out_f = async {
|
||||
let args_and_out_f = async {
|
||||
create_args_and_out_file(&client, job, job_dir, db).await?;
|
||||
if !annotation.native_mode {
|
||||
create_args_and_out_file(&client, job, job_dir, db).await?;
|
||||
}
|
||||
Ok(()) as Result<()>
|
||||
};
|
||||
let reserved_variables_f = async {
|
||||
@@ -898,7 +982,10 @@ try {{
|
||||
Ok(reserved_variables) as error::Result<HashMap<String, String>>
|
||||
};
|
||||
|
||||
let build_cache = !bundle_cache && !codebase.is_some() && requirements_o.is_some();
|
||||
let build_cache = !has_bundle_cache
|
||||
&& !annotation.nobundling
|
||||
&& !codebase.is_some()
|
||||
&& (requirements_o.is_some() || annotation.native_mode);
|
||||
|
||||
let write_loader_f = async {
|
||||
if build_cache {
|
||||
@@ -910,6 +997,8 @@ try {{
|
||||
&job.script_path(),
|
||||
if annotation.nodejs_mode {
|
||||
LoaderMode::NodeBundle
|
||||
} else if annotation.native_mode {
|
||||
LoaderMode::BrowserBundle
|
||||
} else {
|
||||
LoaderMode::BunBundle
|
||||
},
|
||||
@@ -917,7 +1006,7 @@ try {{
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
} else if !codebase.is_some() && !bundle_cache {
|
||||
} else if !codebase.is_some() && !has_bundle_cache {
|
||||
build_loader(
|
||||
job_dir,
|
||||
base_internal_url,
|
||||
@@ -941,7 +1030,7 @@ try {{
|
||||
write_wrapper_f,
|
||||
write_loader_f
|
||||
)?;
|
||||
if !codebase.is_some() && !bundle_cache {
|
||||
if !codebase.is_some() && !has_bundle_cache {
|
||||
if build_cache {
|
||||
generate_bun_bundle(
|
||||
job_dir,
|
||||
@@ -955,31 +1044,35 @@ try {{
|
||||
&common_bun_proc_envs,
|
||||
)
|
||||
.await?;
|
||||
match save_cache(&local_path, &remote_path, &format!("{job_dir}/main.js")).await {
|
||||
Err(e) => {
|
||||
let em = format!("could not save {local_path} to go cache: {e:?}");
|
||||
tracing::error!(em)
|
||||
}
|
||||
Ok(logs) => {
|
||||
init_logs.push_str(&"\n");
|
||||
init_logs.push_str(&logs);
|
||||
init_logs.push_str(&"\n");
|
||||
tracing::info!("saved bun bundle cache: {logs}")
|
||||
if !local_path.is_empty() {
|
||||
match save_cache(&local_path, &remote_path, &format!("{job_dir}/main.js")).await {
|
||||
Err(e) => {
|
||||
let em = format!("could not save {local_path} to bundle cache: {e:?}");
|
||||
tracing::error!(em)
|
||||
}
|
||||
Ok(logs) => {
|
||||
init_logs.push_str(&"\n");
|
||||
init_logs.push_str(&logs);
|
||||
init_logs.push_str(&"\n");
|
||||
tracing::info!("saved bun bundle cache: {logs}")
|
||||
}
|
||||
}
|
||||
}
|
||||
let ex_wrapper = read_file_content(&format!("{job_dir}/wrapper.mjs")).await?;
|
||||
write_file(
|
||||
job_dir,
|
||||
"wrapper.mjs",
|
||||
&ex_wrapper.replace(
|
||||
"import * as Main from \"./main.ts\"",
|
||||
"import * as Main from \"./main.js\"",
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?;
|
||||
if !annotation.native_mode {
|
||||
let ex_wrapper = read_file_content(&format!("{job_dir}/wrapper.mjs")).await?;
|
||||
write_file(
|
||||
job_dir,
|
||||
"wrapper.mjs",
|
||||
&ex_wrapper.replace(
|
||||
"import * as Main from \"./main.ts\"",
|
||||
"import * as Main from \"./main.js\"",
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?;
|
||||
}
|
||||
fs::remove_file(format!("{job_dir}/main.ts"))?;
|
||||
bundle_cache = true;
|
||||
has_bundle_cache = true;
|
||||
} else if annotation.nodejs_mode {
|
||||
generate_wrapper_mjs(
|
||||
job_dir,
|
||||
@@ -995,6 +1088,44 @@ try {{
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
if annotation.native_mode {
|
||||
let env_code = format!(
|
||||
"const process = {{ env: {{}} }};\nconst BASE_URL = '{base_internal_url}';\nconst BASE_INTERNAL_URL = '{base_internal_url}';\nprocess.env['BASE_URL'] = BASE_URL;process.env['BASE_INTERNAL_URL'] = BASE_INTERNAL_URL;\n{}",
|
||||
reserved_variables
|
||||
.iter()
|
||||
.map(|(k, v)| format!("process.env['{}'] = '{}';\n", k, v))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n"));
|
||||
let js_code = read_file_content(&format!("{job_dir}/main.js")).await?;
|
||||
let started_at = Instant::now();
|
||||
let result = crate::js_eval::eval_fetch_timeout(
|
||||
env_code,
|
||||
inner_content.clone(),
|
||||
js_code,
|
||||
job.args.as_ref(),
|
||||
job.id,
|
||||
job.timeout,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!(
|
||||
"Executed native code in {}ms",
|
||||
started_at.elapsed().as_millis()
|
||||
);
|
||||
append_logs(
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
format!("{}\n{}", init_logs, result.1),
|
||||
db,
|
||||
)
|
||||
.await;
|
||||
return Ok(result.0);
|
||||
}
|
||||
append_logs(&job.id, &job.workspace_id, init_logs, db).await;
|
||||
|
||||
//do not cache local dependencies
|
||||
@@ -1037,7 +1168,7 @@ try {{
|
||||
&NODE_PATH,
|
||||
"/tmp/nodejs/wrapper.mjs",
|
||||
]
|
||||
} else if codebase.is_some() || bundle_cache {
|
||||
} else if codebase.is_some() || has_bundle_cache {
|
||||
vec![
|
||||
"--config",
|
||||
"run.config.proto",
|
||||
@@ -1091,7 +1222,7 @@ try {{
|
||||
let script_path = format!("{job_dir}/wrapper.mjs");
|
||||
|
||||
let mut bun_cmd = Command::new(&*BUN_PATH);
|
||||
let args = if codebase.is_some() || bundle_cache {
|
||||
let args = if codebase.is_some() || has_bundle_cache {
|
||||
vec!["run", &script_path]
|
||||
} else {
|
||||
vec![
|
||||
@@ -1189,8 +1320,6 @@ pub async fn start_worker(
|
||||
tracing::info!("Starting worker {w_id};{script_path} (codebase: {codebase:?}");
|
||||
if !codebase.is_some() {
|
||||
let _ = write_file(job_dir, "main.ts", inner_content).await?;
|
||||
} else {
|
||||
let _ = write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?;
|
||||
}
|
||||
|
||||
let common_bun_proc_envs: HashMap<String, String> =
|
||||
|
||||
@@ -111,7 +111,7 @@ pub async fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> {
|
||||
tracing::info!("Failed to untar to {folder}. Error: {:?}", e);
|
||||
fs::remove_dir_all(&folder).await?;
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to untar piptar {folder}"
|
||||
"Failed to untar tar {folder}"
|
||||
)));
|
||||
}
|
||||
tracing::info!(
|
||||
|
||||
@@ -12,7 +12,7 @@ use windmill_common::{
|
||||
error::{self, Error},
|
||||
jobs::QueuedJob,
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, CLOUD_HOSTED},
|
||||
worker::save_cache,
|
||||
};
|
||||
use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE};
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
@@ -301,12 +301,6 @@ func Run(req Req) (interface{{}}, error){{
|
||||
)
|
||||
.await?;
|
||||
|
||||
if cache && *CLOUD_HOSTED {
|
||||
//do not keep the binary in the cache if it is cloud hosted to avoid filling up the disk
|
||||
if let Err(e) = tokio::fs::remove_file(&bin_path).await {
|
||||
tracing::error!("could not remove {bin_path} from go cache: {e:?}");
|
||||
}
|
||||
}
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ impl ContainerRootCertStoreProvider {
|
||||
fn add_certificate(&mut self, cert_path: String) -> io::Result<()> {
|
||||
let cert_file = std::fs::File::open(cert_path)?;
|
||||
let mut reader = BufReader::new(cert_file);
|
||||
let pem_file = rustls_pemfile::certs(&mut reader)?;
|
||||
let pem_file = rustls_pemfile::certs(&mut reader).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
self.root_cert_store.add_parsable_certificates(&pem_file);
|
||||
self.root_cert_store.add_parsable_certificates(pem_file);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -189,22 +189,25 @@ pub async fn eval_timeout(
|
||||
}
|
||||
|
||||
if by_id.is_some() && authed_client.is_some() {
|
||||
if let Some(x) = RE_FULL
|
||||
.captures(&expr)
|
||||
.and_then(|x| x.get(1).map(|y| y.as_str()))
|
||||
{
|
||||
// tracing::error!("{:?}", x.split(".").collect::<Vec<_>>());
|
||||
let arr = x.split(".").collect::<Vec<_>>();
|
||||
let mut iter = arr.iter();
|
||||
iter.next();
|
||||
if let Some(id) = iter.next() {
|
||||
let path = iter.join(".");
|
||||
let query = if path.is_empty() { None } else { Some(path) };
|
||||
return authed_client
|
||||
.unwrap()
|
||||
.get_result_by_id(&by_id.as_ref().unwrap().flow_job.to_string(), id, query)
|
||||
.await;
|
||||
}
|
||||
if let Some((id, idx_o, rest)) = RE_FULL.captures(&expr).map(|x| {
|
||||
(
|
||||
x.get(1).unwrap().as_str(),
|
||||
x.get(2).map(|y| y.as_str()),
|
||||
x.get(3).map(|y| y.as_str()),
|
||||
)
|
||||
}) {
|
||||
let query = if let Some(idx) = idx_o {
|
||||
match rest {
|
||||
Some(rest) => Some(format!("{}{}", idx, rest)),
|
||||
None => Some(idx.to_string()),
|
||||
}
|
||||
} else {
|
||||
rest.map(|x| x.trim_start_matches('.').to_string())
|
||||
};
|
||||
return authed_client
|
||||
.unwrap()
|
||||
.get_result_by_id(&by_id.as_ref().unwrap().flow_job.to_string(), id, query)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +337,11 @@ fn replace_with_await(expr: String, fn_name: &str) -> String {
|
||||
}
|
||||
lazy_static! {
|
||||
static ref RE: Regex =
|
||||
Regex::new(r#"(?m)(?P<r>results(?:(?:\.(?:[a-z]|[A-Z]|_|[1-9])+)|(?:\[\".*?\"\])))"#)
|
||||
.unwrap();
|
||||
Regex::new(r#"(?m)(?P<r>results(?:(?:\.[a-zA-Z_0-9]+)|(?:\[\".*?\"\])))"#).unwrap();
|
||||
static ref RE_FULL: Regex =
|
||||
Regex::new(r"(?m)^results((?:\.(?:(?:[a-z]|[A-Z]|_|[1-9])+))+)$").unwrap();
|
||||
Regex::new(r"(?m)^results\.([a-zA-Z_0-9]+)(?:\[(\d+)\])?((?:\.[a-zA-Z_0-9]+)+)?$").unwrap();
|
||||
static ref RE_PROXY: Regex =
|
||||
Regex::new(r"^(https?)://(([^:@\s]+):([^:@\s]+)@)?([^:@\s]+)(:(\d+))?$").unwrap();
|
||||
}
|
||||
|
||||
fn replace_with_await_result(expr: String) -> String {
|
||||
@@ -626,6 +630,51 @@ pub struct LogString {
|
||||
pub s: String,
|
||||
}
|
||||
|
||||
pub struct NativeAnnotation {
|
||||
pub useragent: Option<String>,
|
||||
pub proxy: Option<(String, Option<(String, String)>)>,
|
||||
}
|
||||
|
||||
pub fn get_annotation(inner_content: &str) -> NativeAnnotation {
|
||||
let mut res = NativeAnnotation { useragent: None, proxy: None };
|
||||
|
||||
let anns = inner_content
|
||||
.lines()
|
||||
.take_while(|x| x.starts_with("//"))
|
||||
.map(|x| x.to_string().trim_start_matches("//").trim().to_string())
|
||||
.collect_vec();
|
||||
|
||||
for ann in anns.iter() {
|
||||
if ann.starts_with("useragent") {
|
||||
res.useragent = Some(ann.trim_start_matches("useragent").trim().to_string());
|
||||
} else if ann.starts_with("proxy") {
|
||||
res.proxy = capture_proxy(ann.trim_start_matches("proxy").trim());
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> {
|
||||
RE_PROXY.captures(s).map(|x| {
|
||||
(
|
||||
format!(
|
||||
"{}://{}{}",
|
||||
x.get(1).map(|x| x.as_str()).unwrap_or_default(),
|
||||
x.get(5).map(|x| x.as_str()).unwrap_or_default(),
|
||||
x.get(7)
|
||||
.map(|x| format!(":{}", x.as_str()))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
x.get(3).map(|y| {
|
||||
(
|
||||
y.as_str().to_string(),
|
||||
x.get(4).map(|x| x.as_str().to_string()).unwrap_or_default(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn eval_fetch_timeout(
|
||||
env_code: String,
|
||||
ts_expr: String,
|
||||
@@ -638,6 +687,7 @@ pub async fn eval_fetch_timeout(
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
load_client: bool,
|
||||
) -> anyhow::Result<(Box<RawValue>, String)> {
|
||||
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
|
||||
|
||||
@@ -650,20 +700,45 @@ pub async fn eval_fetch_timeout(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ann = get_annotation(&ts_expr);
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if ann.proxy.is_some() {
|
||||
return Err(Error::ExecutionErr("Proxy is an EE feature".to_string()).into());
|
||||
}
|
||||
|
||||
let mut extra_logs = String::new();
|
||||
if ann.useragent.is_some() {
|
||||
extra_logs.push_str(&format!("useragent: {}\n", ann.useragent.as_ref().unwrap()));
|
||||
}
|
||||
if ann.proxy.is_some() {
|
||||
let (proxy, auth) = ann.proxy.as_ref().unwrap();
|
||||
extra_logs.push_str(&format!(
|
||||
"proxy: {proxy} (basic auth: {})\n",
|
||||
auth.is_some()
|
||||
));
|
||||
}
|
||||
|
||||
let result_f = tokio::task::spawn_blocking(move || {
|
||||
let ops = vec![op_get_static_args(), op_log()];
|
||||
let ext = Extension { name: "windmill", ops: ops.into(), ..Default::default() };
|
||||
|
||||
let deno_fetch_options = if let Some(cert_path) = env::var("DENO_CERT").ok() {
|
||||
let mut cert_store_provider = ContainerRootCertStoreProvider::new();
|
||||
cert_store_provider.add_certificate(cert_path)?;
|
||||
|
||||
deno_fetch::Options {
|
||||
root_cert_store_provider: Some(Arc::new(cert_store_provider)),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
Default::default()
|
||||
let fetch_options = deno_fetch::Options {
|
||||
root_cert_store_provider: if let Some(cert_path) = env::var("DENO_CERT").ok() {
|
||||
let mut cert_store_provider = ContainerRootCertStoreProvider::new();
|
||||
cert_store_provider.add_certificate(cert_path)?;
|
||||
Some(Arc::new(cert_store_provider))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
user_agent: ann.useragent.unwrap_or_else(|| "windmill/beta".to_string()),
|
||||
proxy: ann.proxy.map(|x| deno_tls::Proxy {
|
||||
url: x.0,
|
||||
basic_auth: x
|
||||
.1
|
||||
.map(|(username, password)| deno_tls::BasicAuth { username, password }),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let exts: Vec<Extension> = vec![
|
||||
@@ -674,7 +749,7 @@ pub async fn eval_fetch_timeout(
|
||||
Arc::new(BlobStore::default()),
|
||||
None,
|
||||
),
|
||||
deno_fetch::deno_fetch::init_ops::<PermissionsContainer>(deno_fetch_options),
|
||||
deno_fetch::deno_fetch::init_ops::<PermissionsContainer>(fetch_options),
|
||||
deno_net::deno_net::init_ops::<PermissionsContainer>(None, None),
|
||||
ext,
|
||||
];
|
||||
@@ -732,7 +807,7 @@ pub async fn eval_fetch_timeout(
|
||||
|
||||
let future = async {
|
||||
tokio::select! {
|
||||
r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code)) => Ok(r),
|
||||
r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), load_client) => Ok(r),
|
||||
_ = memory_limit_rx.recv() => Err(Error::ExecutionErr("Memory limit reached, killing isolate".to_string()))
|
||||
}
|
||||
};
|
||||
@@ -752,7 +827,7 @@ pub async fn eval_fetch_timeout(
|
||||
})
|
||||
});
|
||||
|
||||
let r = run_future_with_polling_update_job_poller(
|
||||
let (res, logs) = run_future_with_polling_update_job_poller(
|
||||
job_id,
|
||||
job_timeout,
|
||||
db,
|
||||
@@ -769,8 +844,8 @@ pub async fn eval_fetch_timeout(
|
||||
}
|
||||
e
|
||||
})?;
|
||||
*mem_peak = (r.0.get().len() / 1000) as i32;
|
||||
Ok(r)
|
||||
*mem_peak = (res.get().len() / 1000) as i32;
|
||||
Ok((res, format!("{extra_logs}{logs}")))
|
||||
}
|
||||
|
||||
const WINDMILL_CLIENT: &str = include_str!("./windmill-client.js");
|
||||
@@ -779,14 +854,17 @@ async fn eval_fetch(
|
||||
js_runtime: &mut JsRuntime,
|
||||
expr: &str,
|
||||
env_code: Option<String>,
|
||||
load_client: bool,
|
||||
) -> anyhow::Result<Box<RawValue>> {
|
||||
if let Some(env_code) = env_code.as_ref() {
|
||||
let _ = js_runtime
|
||||
.load_side_es_module_from_code(
|
||||
&deno_core::resolve_url("file:///windmill.ts")?,
|
||||
format!("{env_code}\n{}", WINDMILL_CLIENT.to_string()),
|
||||
)
|
||||
.await?;
|
||||
if load_client {
|
||||
if let Some(env_code) = env_code.as_ref() {
|
||||
let _ = js_runtime
|
||||
.load_side_es_module_from_code(
|
||||
&deno_core::resolve_url("file:///windmill.ts")?,
|
||||
format!("{env_code}\n{}", WINDMILL_CLIENT.to_string()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = js_runtime
|
||||
|
||||
@@ -29,6 +29,7 @@ globalThis.atob = base64.atob;
|
||||
globalThis.btoa = base64.btoa;
|
||||
globalThis.fetch = fetch.fetch;
|
||||
globalThis.Request = request.Request;
|
||||
globalThis.Response = response.Response;
|
||||
globalThis.Blob = file.Blob;
|
||||
globalThis.URL = url.URL;
|
||||
globalThis.FormData = formData.FormData;
|
||||
|
||||
@@ -4,6 +4,7 @@ use core::fmt::Write;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
|
||||
use reqwest::Response;
|
||||
use serde_json::{json, value::RawValue, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
@@ -44,6 +45,12 @@ struct SnowflakeDatabase {
|
||||
struct SnowflakeResponse {
|
||||
data: Vec<Vec<Value>>,
|
||||
resultSetMetaData: SnowflakeResultSetMetaData,
|
||||
statementHandle: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SnowflakeDataOnlyResponse {
|
||||
data: Vec<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -51,6 +58,7 @@ struct SnowflakeResponse {
|
||||
struct SnowflakeResultSetMetaData {
|
||||
numRows: i64,
|
||||
rowType: Vec<SnowflakeRowType>,
|
||||
partitionInfo: Vec<Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -65,6 +73,38 @@ struct SnowflakeError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
trait SnowflakeResponseExt {
|
||||
async fn get_snowflake_response<T: for<'a> Deserialize<'a>>(
|
||||
self,
|
||||
) -> windmill_common::error::Result<T>;
|
||||
}
|
||||
|
||||
impl SnowflakeResponseExt for Result<Response, reqwest::Error> {
|
||||
async fn get_snowflake_response<T: for<'a> Deserialize<'a>>(
|
||||
self,
|
||||
) -> windmill_common::error::Result<T> {
|
||||
match self {
|
||||
Ok(response) => match response.error_for_status_ref() {
|
||||
Ok(_) => response
|
||||
.json::<T>()
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string())),
|
||||
Err(e) => {
|
||||
let resp = response.text().await.unwrap_or("".to_string());
|
||||
match serde_json::from_str::<SnowflakeError>(&resp) {
|
||||
Ok(sf_err) => return Err(Error::ExecutionErr(sf_err.message)),
|
||||
Err(_) => return Err(Error::ExecutionErr(e.to_string())),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => Err(Error::ExecutionErr(format!(
|
||||
"Could not send request: {:?}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_snowflake_inner<'a>(
|
||||
query: &'a str,
|
||||
job_args: &HashMap<String, Value>,
|
||||
@@ -105,59 +145,66 @@ fn do_snowflake_inner<'a>(
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
|
||||
.get_snowflake_response::<SnowflakeResponse>()
|
||||
.await?;
|
||||
|
||||
match response.error_for_status_ref() {
|
||||
Ok(_) => {
|
||||
let result = response
|
||||
.json::<SnowflakeResponse>()
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
|
||||
if response.resultSetMetaData.numRows > 10000 {
|
||||
return Err(Error::ExecutionErr(
|
||||
"More than 10000 rows were requested, use LIMIT 10000 to limit the number of rows"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(column_order) = column_order {
|
||||
*column_order = Some(
|
||||
response
|
||||
.resultSetMetaData
|
||||
.rowType
|
||||
.iter()
|
||||
.map(|x| x.name.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
}
|
||||
|
||||
if result.resultSetMetaData.numRows > 10000 {
|
||||
return Err(Error::ExecutionErr(
|
||||
"More than 10000 rows were requested, use LIMIT 10000 to limit the number of rows".to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(column_order) = column_order {
|
||||
*column_order = Some(
|
||||
result
|
||||
.resultSetMetaData
|
||||
.rowType
|
||||
.iter()
|
||||
.map(|x| x.name.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
}
|
||||
let rows = to_raw_value(
|
||||
&result
|
||||
.data
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let mut row_map = serde_json::Map::new();
|
||||
row.iter()
|
||||
.zip(result.resultSetMetaData.rowType.iter())
|
||||
.for_each(|(val, row_type)| {
|
||||
row_map.insert(
|
||||
row_type.name.clone(),
|
||||
parse_val(&val, &row_type.r#type),
|
||||
);
|
||||
});
|
||||
row_map
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
let mut rows = response.data;
|
||||
|
||||
if response.resultSetMetaData.partitionInfo.len() > 1 {
|
||||
for idx in 1..response.resultSetMetaData.partitionInfo.len() {
|
||||
let url = format!(
|
||||
"https://{}.snowflakecomputing.com/api/v2/statements/{}",
|
||||
account_identifier.to_uppercase(),
|
||||
response.statementHandle
|
||||
);
|
||||
let response = HTTP_CLIENT
|
||||
.get(url)
|
||||
.bearer_auth(token)
|
||||
.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT")
|
||||
.query(&[("partition", idx.to_string())])
|
||||
.send()
|
||||
.await
|
||||
.get_snowflake_response::<SnowflakeDataOnlyResponse>()
|
||||
.await?;
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
Err(e) => {
|
||||
let resp = response.text().await.unwrap_or("".to_string());
|
||||
match serde_json::from_str::<SnowflakeError>(&resp) {
|
||||
Ok(sf_err) => Err(Error::ExecutionErr(sf_err.message)),
|
||||
Err(_) => Err(Error::ExecutionErr(e.to_string())),
|
||||
}
|
||||
rows.extend(response.data);
|
||||
}
|
||||
}
|
||||
|
||||
let rows = to_raw_value(
|
||||
&rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let mut row_map = serde_json::Map::new();
|
||||
row.iter()
|
||||
.zip(response.resultSetMetaData.rowType.iter())
|
||||
.for_each(|(val, row_type)| {
|
||||
row_map
|
||||
.insert(row_type.name.clone(), parse_val(&val, &row_type.r#type));
|
||||
});
|
||||
row_map
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
Ok(rows)
|
||||
};
|
||||
|
||||
Ok(result_f.boxed())
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
use windmill_common::{
|
||||
auth::{fetch_authed_from_permissioned_as, JWTAuthClaims, JobPerms, JWT_SECRET},
|
||||
worker::{get_windmill_memory_usage, get_worker_memory_usage, TMP_DIR},
|
||||
auth::{fetch_authed_from_permissioned_as, JWTAuthClaims, JobPerms, JWT_SECRET}, scripts::PREVIEW_IS_TAR_CODEBASE_HASH, worker::{get_windmill_memory_usage, get_worker_memory_usage, TMP_DIR}
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@@ -287,10 +286,14 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub static ref JOB_TOKEN: Option<String> = std::env::var("JOB_TOKEN").ok();
|
||||
|
||||
static ref SLEEP_QUEUE: u64 = std::env::var("SLEEP_QUEUE")
|
||||
pub static ref SLEEP_QUEUE: u64 = std::env::var("SLEEP_QUEUE")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.unwrap_or(DEFAULT_SLEEP_QUEUE);
|
||||
.unwrap_or(DEFAULT_SLEEP_QUEUE * std::env::var("NUM_WORKERS")
|
||||
.ok()
|
||||
.map(|x| x.parse().ok())
|
||||
.flatten()
|
||||
.unwrap_or(2) / 2);
|
||||
|
||||
|
||||
pub static ref DISABLE_NUSER: bool = std::env::var("DISABLE_NUSER")
|
||||
@@ -573,10 +576,10 @@ async fn handle_receive_completed_job<
|
||||
R: rsmq_async::RsmqConnection + Send + Sync + Clone + 'static,
|
||||
>(
|
||||
jc: JobCompleted,
|
||||
base_internal_url: String,
|
||||
db: Pool<Postgres>,
|
||||
worker_dir: String,
|
||||
same_worker_tx: Sender<SameWorkerPayload>,
|
||||
base_internal_url: &str,
|
||||
db: &Pool<Postgres>,
|
||||
worker_dir: &str,
|
||||
same_worker_tx: &Sender<SameWorkerPayload>,
|
||||
rsmq: Option<R>,
|
||||
worker_name: &str,
|
||||
worker_save_completed_job_duration: Option<Histo>,
|
||||
@@ -597,7 +600,7 @@ async fn handle_receive_completed_job<
|
||||
if let Err(err) = process_completed_job(
|
||||
jc,
|
||||
&client,
|
||||
&db,
|
||||
db,
|
||||
&worker_dir,
|
||||
same_worker_tx.clone(),
|
||||
rsmq.clone(),
|
||||
@@ -609,7 +612,7 @@ async fn handle_receive_completed_job<
|
||||
.await
|
||||
{
|
||||
handle_job_error(
|
||||
&db,
|
||||
db,
|
||||
&client,
|
||||
job.as_ref(),
|
||||
mem_peak,
|
||||
@@ -1189,140 +1192,40 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
|
||||
if let Some(wj) = worker_job_completed_channel_queue2.as_ref() {
|
||||
wj.dec();
|
||||
}
|
||||
let base_internal_url2 = base_internal_url2.clone();
|
||||
let worker_dir2 = worker_dir2.clone();
|
||||
let db2 = db2.clone();
|
||||
let same_worker_tx2 = same_worker_tx2.clone();
|
||||
let rsmq2 = rsmq2.clone();
|
||||
let worker_name = worker_name2.clone();
|
||||
if matches!(jc.job.job_kind, JobKind::Noop) {
|
||||
thread_count.fetch_add(1, Ordering::SeqCst);
|
||||
let thread_count = thread_count.clone();
|
||||
|
||||
loop {
|
||||
if thread_count.load(Ordering::Relaxed) < 4 {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(3)).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "benchmark")]
|
||||
let send_duration = send_duration2.clone();
|
||||
#[cfg(feature = "benchmark")]
|
||||
let process_duration = process_duration.clone();
|
||||
#[cfg(feature = "benchmark")]
|
||||
let completed_jobs = completed_jobs.clone();
|
||||
#[cfg(feature = "benchmark")]
|
||||
let main_duration = main_duration2.clone();
|
||||
|
||||
#[cfg(feature = "prometheus")]
|
||||
let worker_save_completed_job_duration2 =
|
||||
worker_save_completed_job_duration.clone();
|
||||
#[cfg(feature = "prometheus")]
|
||||
let worker_flow_transition_duration2 =
|
||||
worker_flow_transition_duration.clone();
|
||||
let killpill_tx = killpill_tx2.clone();
|
||||
let job_completed_sender = job_completed_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
#[cfg(feature = "benchmark")]
|
||||
let process_start = Instant::now();
|
||||
|
||||
let is_dependency_job = matches!(
|
||||
jc.job.job_kind,
|
||||
JobKind::Dependencies | JobKind::FlowDependencies
|
||||
);
|
||||
handle_receive_completed_job(
|
||||
jc,
|
||||
base_internal_url2,
|
||||
db2.clone(),
|
||||
worker_dir2,
|
||||
same_worker_tx2,
|
||||
rsmq2,
|
||||
&worker_name,
|
||||
worker_save_completed_job_duration2.clone(),
|
||||
worker_flow_transition_duration2.clone(),
|
||||
job_completed_sender.clone(),
|
||||
)
|
||||
.await;
|
||||
#[cfg(feature = "benchmark")]
|
||||
{
|
||||
let n = completed_jobs.fetch_add(1, Ordering::SeqCst);
|
||||
if (n + 1) % 1000 == 0 || n == (jobs - 1) as usize {
|
||||
let duration_s = start.elapsed().as_secs_f64();
|
||||
let jobs_per_sec = n as f64 / duration_s;
|
||||
tracing::info!(
|
||||
"completed {} jobs in {}s, {} jobs/s",
|
||||
n + 1,
|
||||
duration_s,
|
||||
jobs_per_sec
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
"main loop without send {}s",
|
||||
main_duration.load(Ordering::SeqCst) as f64 / 1000.0
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
"send job completed / send dedicated job duration {}s",
|
||||
send_duration.load(Ordering::SeqCst) as f64 / 1000.0
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
"job completed process duration {}s",
|
||||
process_duration.load(Ordering::SeqCst) as f64 / 1000.0
|
||||
);
|
||||
}
|
||||
|
||||
process_duration.fetch_add(
|
||||
process_start.elapsed().as_millis() as usize,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
}
|
||||
|
||||
thread_count.fetch_sub(1, Ordering::SeqCst);
|
||||
if is_dependency_job && is_dedicated_worker {
|
||||
tracing::error!("Dedicated worker executed a dependency job, a new script has been deployed. Exiting expecting to be restarted.");
|
||||
sqlx::query!("UPDATE config SET config = config WHERE name = $1", format!("worker__{}", *WORKER_GROUP))
|
||||
.execute(&db2)
|
||||
.await
|
||||
.expect("update config to trigger restart of all dedicated workers at that config");
|
||||
killpill_tx.send(()).unwrap_or_default();
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let is_init_script_and_failure =
|
||||
!jc.success && jc.job.tag.as_str() == INIT_SCRIPT_TAG;
|
||||
let is_dependency_job = matches!(
|
||||
jc.job.job_kind,
|
||||
JobKind::Dependencies | JobKind::FlowDependencies);
|
||||
handle_receive_completed_job(
|
||||
jc,
|
||||
base_internal_url2,
|
||||
db2.clone(),
|
||||
worker_dir2,
|
||||
same_worker_tx2,
|
||||
rsmq2,
|
||||
&worker_name,
|
||||
worker_save_completed_job_duration2.clone(),
|
||||
worker_flow_transition_duration2.clone(),
|
||||
job_completed_sender.clone(),
|
||||
)
|
||||
.await;
|
||||
if is_init_script_and_failure {
|
||||
tracing::error!("init script errored, exiting");
|
||||
killpill_tx2.send(()).unwrap_or_default();
|
||||
}
|
||||
if is_dependency_job && is_dedicated_worker {
|
||||
tracing::error!("Dedicated worker executed a dependency job, a new script has been deployed. Exiting expecting to be restarted.");
|
||||
sqlx::query!("UPDATE config SET config = config WHERE name = $1", format!("worker__{}", *WORKER_GROUP))
|
||||
.execute(&db2)
|
||||
.await
|
||||
.expect("update config to trigger restart of all dedicated workers at that config");
|
||||
killpill_tx2.send(()).unwrap_or_default();
|
||||
|
||||
}
|
||||
let is_init_script_and_failure =
|
||||
!jc.success && jc.job.tag.as_str() == INIT_SCRIPT_TAG;
|
||||
let is_dependency_job = matches!(
|
||||
jc.job.job_kind,
|
||||
JobKind::Dependencies | JobKind::FlowDependencies);
|
||||
handle_receive_completed_job(
|
||||
jc,
|
||||
&base_internal_url2,
|
||||
&db2,
|
||||
&worker_dir2,
|
||||
&same_worker_tx2,
|
||||
rsmq2,
|
||||
&worker_name2,
|
||||
worker_save_completed_job_duration2.clone(),
|
||||
worker_flow_transition_duration2.clone(),
|
||||
job_completed_sender.clone(),
|
||||
)
|
||||
.await;
|
||||
if is_init_script_and_failure {
|
||||
tracing::error!("init script errored, exiting");
|
||||
killpill_tx2.send(()).unwrap_or_default();
|
||||
}
|
||||
if is_dependency_job && is_dedicated_worker {
|
||||
tracing::error!("Dedicated worker executed a dependency job, a new script has been deployed. Exiting expecting to be restarted.");
|
||||
sqlx::query!("UPDATE config SET config = config WHERE name = $1", format!("worker__{}", *WORKER_GROUP))
|
||||
.execute(&db2)
|
||||
.await
|
||||
.expect("update config to trigger restart of all dedicated workers at that config");
|
||||
killpill_tx2.send(()).unwrap_or_default();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
SendResult::UpdateFlow {
|
||||
flow,
|
||||
@@ -2339,6 +2242,7 @@ async fn do_nativets(
|
||||
canceled_by,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
Ok((result.0, result.1))
|
||||
@@ -2374,6 +2278,34 @@ async fn handle_queued_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
return Err(Error::ExecutionErr(e.to_string()));
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "enterprise"), feature = "sqlx"))]
|
||||
if job.created_by.starts_with("email-trigger-") {
|
||||
let daily_count = sqlx::query!(
|
||||
"SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1"
|
||||
).fetch_optional(db).await?.map(|x| serde_json::from_value::<i64>(x.value).unwrap_or(1));
|
||||
|
||||
if let Some(count) = daily_count {
|
||||
if count >= 100 {
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Email trigger usage limit of 100 per day has been reached."
|
||||
)));
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'",
|
||||
serde_json::json!(count + 1)
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))"
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let step = if job.is_flow_step {
|
||||
let r = update_flow_status_in_progress(
|
||||
db,
|
||||
@@ -2838,10 +2770,10 @@ pub async fn get_script_content_by_hash(
|
||||
Option<String>,
|
||||
Option<ScriptLang>,
|
||||
Option<Vec<String>>,
|
||||
bool,
|
||||
Option<bool>,
|
||||
),
|
||||
>(
|
||||
"SELECT content, lock, language, envs, codebase IS NOT NULL FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
"SELECT content, lock, language, envs, codebase LIKE '%.tar' as codebase FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(script_hash.0)
|
||||
.bind(w_id)
|
||||
@@ -2853,8 +2785,14 @@ pub async fn get_script_content_by_hash(
|
||||
lockfile: r.1,
|
||||
language: r.2,
|
||||
envs: r.3,
|
||||
codebase: if r.4 {
|
||||
Some(script_hash.to_string())
|
||||
codebase: if r.4.is_some() {
|
||||
let b = r.4.unwrap();
|
||||
let sh = script_hash.to_string();
|
||||
if b {
|
||||
Some(format!("{sh}.tar"))
|
||||
} else {
|
||||
Some(sh)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -2881,23 +2819,23 @@ async fn handle_code_execution_job(
|
||||
envs,
|
||||
codebase,
|
||||
} = match job.job_kind {
|
||||
JobKind::Preview => ContentReqLangEnvs {
|
||||
content: job
|
||||
.raw_code
|
||||
.clone()
|
||||
.unwrap_or_else(|| "no raw code".to_owned()),
|
||||
lockfile: job.raw_lock.clone(),
|
||||
language: job.language.to_owned(),
|
||||
envs: None,
|
||||
codebase: if job
|
||||
.script_hash
|
||||
.is_some_and(|y| y.0 == PREVIEW_IS_CODEBASE_HASH)
|
||||
{
|
||||
Some(job.id.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
JobKind::Preview => {
|
||||
let codebase = match job.script_hash.map(|x| x.0) {
|
||||
Some(PREVIEW_IS_CODEBASE_HASH) => Some(job.id.to_string()),
|
||||
Some(PREVIEW_IS_TAR_CODEBASE_HASH) => Some(format!("{}.tar", job.id)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
ContentReqLangEnvs {
|
||||
content: job
|
||||
.raw_code
|
||||
.clone()
|
||||
.unwrap_or_else(|| "no raw code".to_owned()),
|
||||
lockfile: job.raw_lock.clone(),
|
||||
language: job.language.to_owned(),
|
||||
envs: None,
|
||||
codebase
|
||||
}},
|
||||
JobKind::Script_Hub => {
|
||||
get_hub_script_content_and_requirements(job.script_path.clone(), db).await?
|
||||
}
|
||||
@@ -3122,7 +3060,7 @@ mount {{
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(ScriptLang::Bun) => {
|
||||
Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => {
|
||||
handle_bun_job(
|
||||
requirements_o,
|
||||
codebase,
|
||||
|
||||
@@ -952,21 +952,23 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
let _ = tokio::fs::remove_dir_all(format!("{worker_dir}/{}", flow_job.id)).await;
|
||||
}
|
||||
|
||||
if let Some(parent_job) = flow_job.parent_job {
|
||||
tracing::info!(subflow_id = %flow_job.id, parent_id = %parent_job, "subflow is finished, updating parent flow status");
|
||||
if flow_job.is_flow_step {
|
||||
if let Some(parent_job) = flow_job.parent_job {
|
||||
tracing::info!(subflow_id = %flow_job.id, parent_id = %parent_job, "subflow is finished, updating parent flow status");
|
||||
|
||||
return Ok(Some(RecUpdateFlowStatusAfterJobCompletion {
|
||||
flow: parent_job,
|
||||
job_id_for_status: flow,
|
||||
success: success && !is_failure_step,
|
||||
result: nresult.clone(),
|
||||
stop_early_override: if stop_early {
|
||||
Some(skip_if_stop_early)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
skip_error_handler: skip_error_handler || is_failure_step,
|
||||
}));
|
||||
return Ok(Some(RecUpdateFlowStatusAfterJobCompletion {
|
||||
flow: parent_job,
|
||||
job_id_for_status: flow,
|
||||
success: success && !is_failure_step,
|
||||
result: nresult.clone(),
|
||||
stop_early_override: if stop_early {
|
||||
Some(skip_if_stop_early)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
skip_error_handler: skip_error_handler || is_failure_step,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
|
||||
@@ -12,7 +12,7 @@ use windmill_common::flows::{FlowModule, FlowModuleValue};
|
||||
use windmill_common::get_latest_deployed_hash_for_path;
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::scripts::ScriptHash;
|
||||
use windmill_common::worker::{to_raw_value, to_raw_value_owned};
|
||||
use windmill_common::worker::{get_annotation, to_raw_value, to_raw_value_owned};
|
||||
use windmill_common::{
|
||||
error::{self, to_anyhow},
|
||||
flows::FlowValue,
|
||||
@@ -187,14 +187,16 @@ fn parse_bun_relative_imports(raw_code: &str, script_path: &str) -> error::Resul
|
||||
Ok(relative_imports)
|
||||
}
|
||||
|
||||
fn extract_relative_imports(
|
||||
pub fn extract_relative_imports(
|
||||
raw_code: &str,
|
||||
script_path: &str,
|
||||
language: &Option<ScriptLang>,
|
||||
) -> Option<Vec<String>> {
|
||||
match language {
|
||||
Some(ScriptLang::Python3) => parse_relative_imports(&raw_code, script_path).ok(),
|
||||
Some(ScriptLang::Bun) => parse_bun_relative_imports(&raw_code, script_path).ok(),
|
||||
Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => {
|
||||
parse_bun_relative_imports(&raw_code, script_path).ok()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -286,7 +288,7 @@ pub async fn handle_dependency_job<R: rsmq_async::RsmqConnection + Send + Sync +
|
||||
let hash = job.script_hash.unwrap_or(ScriptHash(0));
|
||||
let w_id = &job.workspace_id;
|
||||
sqlx::query!(
|
||||
"UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
"UPDATE script SET lock = $1, created_at = now() WHERE hash = $2 AND workspace_id = $3",
|
||||
&content,
|
||||
&hash.0,
|
||||
w_id
|
||||
@@ -700,7 +702,7 @@ async fn lock_modules<'c>(
|
||||
lock,
|
||||
path,
|
||||
content,
|
||||
language,
|
||||
mut language,
|
||||
input_transforms,
|
||||
tag,
|
||||
custom_concurrency_key,
|
||||
@@ -852,8 +854,11 @@ async fn lock_modules<'c>(
|
||||
}
|
||||
} else {
|
||||
if lock.as_ref().is_some_and(|x| !x.trim().is_empty()) {
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
let skip_creating_new_lock = skip_creating_new_lock(&language, &content);
|
||||
if skip_creating_new_lock {
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,6 +916,14 @@ async fn lock_modules<'c>(
|
||||
append_logs(&job.id, &job.workspace_id, logs, db).await;
|
||||
}
|
||||
|
||||
if language == ScriptLang::Bun || language == ScriptLang::Bunnative {
|
||||
let anns = get_annotation(&content);
|
||||
if anns.native_mode && language == ScriptLang::Bun {
|
||||
language = ScriptLang::Bunnative;
|
||||
} else if !anns.native_mode && language == ScriptLang::Bunnative {
|
||||
language = ScriptLang::Bun;
|
||||
};
|
||||
}
|
||||
e.value = windmill_common::worker::to_raw_value(&FlowModuleValue::RawScript {
|
||||
lock: Some(new_lock),
|
||||
path,
|
||||
@@ -952,6 +965,18 @@ async fn lock_modules<'c>(
|
||||
Ok((new_flow_modules, tx, modified_ids))
|
||||
}
|
||||
|
||||
fn skip_creating_new_lock(language: &ScriptLang, content: &str) -> bool {
|
||||
if language == &ScriptLang::Bun || language == &ScriptLang::Bunnative {
|
||||
let anns = get_annotation(&content);
|
||||
if anns.native_mode && language == &ScriptLang::Bun {
|
||||
return false;
|
||||
} else if !anns.native_mode && language == &ScriptLang::Bunnative {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn lock_modules_app(
|
||||
value: Value,
|
||||
@@ -985,10 +1010,12 @@ async fn lock_modules_app(
|
||||
if v.get("lock")
|
||||
.is_some_and(|x| !x.as_str().unwrap().trim().is_empty())
|
||||
{
|
||||
logs.push_str(
|
||||
"Found already locked inline script. Skipping lock...\n",
|
||||
);
|
||||
return Ok(Value::Object(m.clone()));
|
||||
if skip_creating_new_lock(&language, &content) {
|
||||
logs.push_str(
|
||||
"Found already locked inline script. Skipping lock...\n",
|
||||
);
|
||||
return Ok(Value::Object(m.clone()));
|
||||
}
|
||||
}
|
||||
logs.push_str("Found lockable inline script. Generating lock...\n");
|
||||
let new_lock = capture_dependency_job(
|
||||
@@ -1012,6 +1039,21 @@ async fn lock_modules_app(
|
||||
match new_lock {
|
||||
Ok(new_lock) => {
|
||||
append_logs(&job.id, &job.workspace_id, logs, db).await;
|
||||
let anns = get_annotation(&content);
|
||||
let nlang = if anns.native_mode && language == ScriptLang::Bun {
|
||||
Some(ScriptLang::Bunnative)
|
||||
} else if !anns.native_mode && language == ScriptLang::Bunnative
|
||||
{
|
||||
Some(ScriptLang::Bun)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(nlang) = nlang {
|
||||
v.insert(
|
||||
"language".to_string(),
|
||||
serde_json::Value::String(nlang.as_str().to_string()),
|
||||
);
|
||||
}
|
||||
v.insert(
|
||||
"lock".to_string(),
|
||||
serde_json::Value::String(new_lock),
|
||||
@@ -1293,7 +1335,7 @@ async fn capture_dependency_job(
|
||||
)
|
||||
.await
|
||||
}
|
||||
ScriptLang::Bun => {
|
||||
ScriptLang::Bun | ScriptLang::Bunnative => {
|
||||
let npm_mode = npm_mode
|
||||
.unwrap_or_else(|| windmill_common::worker::get_annotation(job_raw_code).npm_mode);
|
||||
if !raw_deps {
|
||||
@@ -1319,7 +1361,7 @@ async fn capture_dependency_job(
|
||||
npm_mode,
|
||||
)
|
||||
.await?;
|
||||
if req.is_some() {
|
||||
if req.is_some() && !raw_deps {
|
||||
crate::bun_executor::prebundle_script(
|
||||
job_raw_code,
|
||||
req.clone(),
|
||||
|
||||
@@ -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.368.0";
|
||||
export const VERSION = "v1.377.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -12,7 +12,13 @@ export async function listSyncCodebases(
|
||||
log.info(`Found ${nb_codebase} codebases:`);
|
||||
}
|
||||
for (const codebase of options?.codebases ?? []) {
|
||||
const digest = await digestDir(codebase.relative_path);
|
||||
let digest = await digestDir(
|
||||
codebase.relative_path,
|
||||
JSON.stringify(codebase)
|
||||
);
|
||||
if (Array.isArray(codebase.assets) && codebase.assets.length > 0) {
|
||||
digest += ".tar";
|
||||
}
|
||||
log.info(`Codebase ${codebase.relative_path}, digest: ${digest}`);
|
||||
res.push({ ...codebase, digest });
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ export interface Codebase {
|
||||
relative_path: string;
|
||||
includes?: string[];
|
||||
excludes?: string[];
|
||||
assets?: {
|
||||
from: string;
|
||||
to: string;
|
||||
}[];
|
||||
customBundler?: string;
|
||||
external?: string[];
|
||||
}
|
||||
|
||||
export async function readConfigFile(): Promise<SyncOptions> {
|
||||
|
||||
@@ -34,7 +34,7 @@ addEventListener("error", (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
export const VERSION = "v1.368.0";
|
||||
export const VERSION = "v1.377.1";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -164,7 +164,7 @@ export async function generateFlowLockInternal(
|
||||
hashes = await generateFlowHash(folder);
|
||||
|
||||
for (const [path, hash] of Object.entries(hashes)) {
|
||||
await updateMetadataLock(folder, hash, path);
|
||||
await updateMetadataGlobalLock(folder, hash, path);
|
||||
}
|
||||
log.info(colors.green(`Flow ${remote_path} lockfiles updated`));
|
||||
}
|
||||
@@ -210,8 +210,15 @@ export async function generateScriptMetadataInternal(
|
||||
|
||||
// read script content
|
||||
const scriptContent = await Deno.readTextFile(scriptPath);
|
||||
const metadataContent = await Deno.readTextFile(metadataWithType.path);
|
||||
let metadataContent = await Deno.readTextFile(metadataWithType.path);
|
||||
const c = findCodebase(scriptPath, codebases);
|
||||
|
||||
if (c) {
|
||||
metadataContent += c.digest ?? "";
|
||||
}
|
||||
|
||||
let hash = await generateScriptHash(rawReqs, scriptContent, metadataContent);
|
||||
|
||||
if (await checkifMetadataUptodate(remotePath, hash, undefined)) {
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
@@ -242,7 +249,6 @@ export async function generateScriptMetadataInternal(
|
||||
}
|
||||
|
||||
if (!opts.schemaOnly && !justUpdateMetadataLock) {
|
||||
const c = findCodebase(scriptPath, codebases);
|
||||
if (!c) {
|
||||
await updateScriptLock(
|
||||
workspace,
|
||||
@@ -252,7 +258,9 @@ export async function generateScriptMetadataInternal(
|
||||
metadataParsedContent,
|
||||
rawReqs
|
||||
);
|
||||
metadataParsedContent.codebase = undefined;
|
||||
} else {
|
||||
metadataParsedContent.codebase = c.digest;
|
||||
metadataParsedContent.lock = "";
|
||||
}
|
||||
} else {
|
||||
@@ -266,8 +274,17 @@ export async function generateScriptMetadataInternal(
|
||||
metaPath = remotePath + ".script.json";
|
||||
newMetadataContent = JSON.stringify(metadataParsedContent);
|
||||
}
|
||||
hash = await generateScriptHash(rawReqs, scriptContent, metadataContent);
|
||||
await updateMetadataLock(remotePath, hash);
|
||||
|
||||
let metadataContentUsedForHash = newMetadataContent;
|
||||
if (c) {
|
||||
metadataContentUsedForHash += c.digest ?? "";
|
||||
}
|
||||
hash = await generateScriptHash(
|
||||
rawReqs,
|
||||
scriptContent,
|
||||
metadataContentUsedForHash
|
||||
);
|
||||
await updateMetadataGlobalLock(remotePath, hash);
|
||||
if (!justUpdateMetadataLock) {
|
||||
await Deno.writeTextFile(metaPath, newMetadataContent);
|
||||
}
|
||||
@@ -347,8 +364,17 @@ async function updateScriptLock(
|
||||
);
|
||||
}
|
||||
const lockPath = remotePath + ".script.lock";
|
||||
await Deno.writeTextFile(lockPath, lock);
|
||||
metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
|
||||
if (lock != "") {
|
||||
await Deno.writeTextFile(lockPath, lock);
|
||||
metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
|
||||
} else {
|
||||
try {
|
||||
if (await Deno.stat(lockPath)) {
|
||||
await Deno.remove(lockPath);
|
||||
}
|
||||
} catch {}
|
||||
metadataContent.lock = "";
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`
|
||||
@@ -821,7 +847,7 @@ export async function generateScriptHash(
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateMetadataLock(
|
||||
export async function updateMetadataGlobalLock(
|
||||
path: string,
|
||||
hash: string,
|
||||
subpath?: string
|
||||
|
||||
@@ -46,6 +46,10 @@ import {
|
||||
readConfigFile,
|
||||
} from "./conf.ts";
|
||||
import { SyncCodebase, listSyncCodebases } from "./codebase.ts";
|
||||
import fs from "node:fs";
|
||||
import { type Tarball } from "npm:@ayonli/jsext/archive";
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
export interface ScriptFile {
|
||||
parent_hash?: string;
|
||||
@@ -150,18 +154,51 @@ export async function handleFile(
|
||||
const codebase =
|
||||
language == "bun" ? findCodebase(path, codebases) : undefined;
|
||||
|
||||
let bundleContent: string | undefined = undefined;
|
||||
let bundleContent: string | Tarball | undefined = undefined;
|
||||
|
||||
if (codebase) {
|
||||
const esbuild = await import("npm:esbuild");
|
||||
log.info(`Starting building the bundle for ${path}`);
|
||||
const out = await esbuild.build({
|
||||
entryPoints: [path],
|
||||
format: "esm",
|
||||
bundle: true,
|
||||
write: false,
|
||||
});
|
||||
bundleContent = out.outputFiles[0].text;
|
||||
if (codebase.customBundler) {
|
||||
log.info(`Using custom bundler ${codebase.customBundler} for ${path}`);
|
||||
bundleContent = execSync(
|
||||
codebase.customBundler + " " + path
|
||||
).toString();
|
||||
log.info("Custom bundler executed");
|
||||
} else {
|
||||
const esbuild = await import("npm:esbuild");
|
||||
|
||||
log.info(`Starting building the bundle for ${path}`);
|
||||
const out = await esbuild.build({
|
||||
entryPoints: [path],
|
||||
format: "cjs",
|
||||
bundle: true,
|
||||
write: false,
|
||||
external: codebase.external,
|
||||
platform: "node",
|
||||
packages: "bundle",
|
||||
target: "node20.15.1",
|
||||
});
|
||||
bundleContent = out.outputFiles[0].text;
|
||||
}
|
||||
if (Array.isArray(codebase.assets) && codebase.assets.length > 0) {
|
||||
const archiveNpm = await import("npm:@ayonli/jsext/archive");
|
||||
|
||||
log.info(
|
||||
`Using the following asset configuration: ${JSON.stringify(
|
||||
codebase.assets
|
||||
)}`
|
||||
);
|
||||
const tarball = new archiveNpm.Tarball();
|
||||
tarball.append(
|
||||
new File([bundleContent], "main.js", { type: "text/plain" })
|
||||
);
|
||||
for (const asset of codebase.assets) {
|
||||
const data = fs.readFileSync(asset.from);
|
||||
const blob = new Blob([data], { type: "text/plain" });
|
||||
const file = new File([blob], asset.to);
|
||||
tarball.append(file);
|
||||
}
|
||||
bundleContent = tarball;
|
||||
}
|
||||
log.info(`Finished building the bundle for ${path}`);
|
||||
}
|
||||
const typed = (
|
||||
@@ -186,7 +223,7 @@ export async function handleFile(
|
||||
try {
|
||||
remote = await ScriptService.getScriptByPath({
|
||||
workspace: workspaceId,
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
path: remotePath,
|
||||
});
|
||||
log.debug(`Script ${remotePath} exists on remote`);
|
||||
} catch {
|
||||
@@ -265,7 +302,8 @@ export async function handleFile(
|
||||
typed.priority == Boolean(remote.priority) &&
|
||||
typed.timeout == remote.timeout &&
|
||||
//@ts-ignore
|
||||
typed.concurrency_key == remote["concurrency_key"])
|
||||
typed.concurrency_key == remote["concurrency_key"] &&
|
||||
typed.codebase == remote.codebase)
|
||||
) {
|
||||
log.info(colors.green(`Script ${remotePath} is up to date`));
|
||||
return true;
|
||||
@@ -296,8 +334,31 @@ export async function handleFile(
|
||||
return false;
|
||||
}
|
||||
|
||||
async function streamToBlob(stream: ReadableStream<Uint8Array>): Promise<Blob> {
|
||||
// Create a reader from the stream
|
||||
const reader = stream.getReader();
|
||||
const chunks = [];
|
||||
|
||||
// Read the data from the stream
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
// If stream is finished, break the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Push the chunk to the array
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
// Create a Blob from the chunks
|
||||
const blob = new Blob(chunks);
|
||||
return blob;
|
||||
}
|
||||
|
||||
async function createScript(
|
||||
bundleContent: string | undefined,
|
||||
bundleContent: string | Tarball | undefined,
|
||||
workspaceId: string,
|
||||
body: NewScript,
|
||||
workspace: Workspace
|
||||
@@ -311,7 +372,12 @@ async function createScript(
|
||||
} else {
|
||||
const form = new FormData();
|
||||
form.append("script", JSON.stringify(body));
|
||||
form.append("file", bundleContent);
|
||||
form.append(
|
||||
"file",
|
||||
typeof bundleContent == "string"
|
||||
? bundleContent
|
||||
: await streamToBlob(bundleContent.stream())
|
||||
);
|
||||
|
||||
const url =
|
||||
workspace.remote +
|
||||
@@ -429,6 +495,7 @@ export const exts = [
|
||||
".sql",
|
||||
".gql",
|
||||
".ps1",
|
||||
".php",
|
||||
];
|
||||
|
||||
export function removeExtensionToPath(path: string): string {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user