Compare commits

..

1 Commits

Author SHA1 Message Date
Ruben Fiszel
6ea6258910 all 2024-10-02 14:14:36 +02:00
111 changed files with 996 additions and 3839 deletions

View File

@@ -43,9 +43,6 @@ RUN wget https://golang.org/dl/go1.21.5.linux-amd64.tar.gz && tar -C /usr/local
ENV PATH="${PATH}:/usr/local/go/bin"
ENV GO_PATH=/usr/local/go/bin/go
# Install UV
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /usr/local/cargo/bin/uv /usr/local/bin/uv
ENV TZ=Etc/UTC
ENV PYTHON_VERSION 3.11.4

View File

@@ -44,5 +44,5 @@ jobs:
mkdir frontend/build && cd backend && touch
windmill-api/openapi-deref.yaml &&
DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill
DISABLE_EMBEDDING=true RUST_LOG=info cargo test --features
enterprise,deno_core --all -- --nocapture
DISABLE_EMBEDDING=true RUST_LOG=info cargo test --features enterprise
--all -- --nocapture

View File

@@ -62,7 +62,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc
tags: |
${{ steps.meta-ee-public.outputs.tags }}
labels: |

View File

@@ -1,56 +0,0 @@
name: Build and Publish Windows Worker
on:
push:
tags:
- "v*"
env:
CARGO_INCREMENTAL: 0
SQLX_OFFLINE: true
DISABLE_EMBEDDING: true
RUST_LOG: info
jobs:
cargo_build_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Read EE repo commit hash
shell: pwsh
run: |
$ee_repo_ref = Get-Content .\backend\ee-repo-ref.txt
echo "ee_repo_ref=$ee_repo_ref" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Checkout windmill-ee-private repository
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: Substitute EE code
shell: bash
run: |
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
- name: Cargo build windows
timeout-minutes: 60
run: |
$env:OPENSSL_DIR = "${Env:ProgramFiles}\OpenSSL"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
- name: Rename binary with corresponding architecture
run: |
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
- name: Attach binary to release
uses: softprops/action-gh-release@v2
with:
files: |
./backend/target/release/windmill-ee.exe

View File

@@ -67,7 +67,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,deno_core
features=embedding,parquet,openidconnect
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
${{ steps.meta-public.outputs.tags }}

View File

@@ -1,10 +1,8 @@
env:
REGISTRY: ghcr.io
IMAGE_NAME:
${{ github.event_name != 'pull_request' && 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}',
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
github.event.number) }}
name: Build windmill:main
@@ -77,7 +75,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,jemalloc,deno_core
features=embedding,parquet,openidconnect,jemalloc
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
@@ -138,7 +136,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
@@ -200,7 +198,7 @@ jobs:
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
PYTHON_IMAGE=python:3.12.2-slim-bookworm
tags: |
${{ steps.meta-ee-public-py312.outputs.tags }}

View File

@@ -1,61 +1,5 @@
# Changelog
## [1.405.3](https://github.com/windmill-labs/windmill/compare/v1.405.2...v1.405.3) (2024-10-04)
### Bug Fixes
* fix id save on apps ([b034b07](https://github.com/windmill-labs/windmill/commit/b034b070c075a0fab74678bec1fd8b829d55b204))
## [1.405.2](https://github.com/windmill-labs/windmill/compare/v1.405.1...v1.405.2) (2024-10-03)
### Bug Fixes
* **cli:** fix opts.yes for instance sync ([26659ce](https://github.com/windmill-labs/windmill/commit/26659ce37d2887d5b98dbdbdbba27bab85d4fe3f))
* fix uv path ([19c62ba](https://github.com/windmill-labs/windmill/commit/19c62ba195b1df85c38c748dab7d9f137696a5c3))
## [1.405.1](https://github.com/windmill-labs/windmill/compare/v1.405.0...v1.405.1) (2024-10-03)
### Bug Fixes
* flow picker of flows + precache hub scripts as bundles ([c84e6fd](https://github.com/windmill-labs/windmill/commit/c84e6fd05de2bea426cae61fa25db0323b8770f5))
## [1.405.0](https://github.com/windmill-labs/windmill/compare/v1.404.1...v1.405.0) (2024-10-03)
### Features
* Replace `pip-compile` with `uv` ([#4460](https://github.com/windmill-labs/windmill/issues/4460)) ([b54c9ee](https://github.com/windmill-labs/windmill/commit/b54c9ee657cc88fabe694cae39dc0d3c1918fcbb))
* **worker:** support workers to run natively on windows ([#4446](https://github.com/windmill-labs/windmill/issues/4446)) ([f5c4727](https://github.com/windmill-labs/windmill/commit/f5c472727465dd95f5378bc08ee9bbb983f4d259))
### Bug Fixes
* **cli:** fix set client of instance when passing token and base url ([794c4cd](https://github.com/windmill-labs/windmill/commit/794c4cde3cd47042472dccdf4b60a012014dd26d))
## [1.404.1](https://github.com/windmill-labs/windmill/compare/v1.404.0...v1.404.1) (2024-10-03)
### Bug Fixes
* flow picker of flows ([92f61f0](https://github.com/windmill-labs/windmill/commit/92f61f07ed6d354407d26843e3a270b95bae90bc))
## [1.404.0](https://github.com/windmill-labs/windmill/compare/v1.403.1...v1.404.0) (2024-10-03)
### Features
* **frontend:** add quick access menu in flow editor ([#4415](https://github.com/windmill-labs/windmill/issues/4415)) ([45ccd45](https://github.com/windmill-labs/windmill/commit/45ccd45e306c66931880a9b8fd48bfe684c774ac))
### Bug Fixes
* **cli:** improve schedule path handling on windows ([9ac3b6b](https://github.com/windmill-labs/windmill/commit/9ac3b6b1d5d64d7467dd80506f8a8d772c4630bd))
* fix id editor for app ([8e58e43](https://github.com/windmill-labs/windmill/commit/8e58e4320a31d71c40a5ed352416a4c2dd3adb26))
* **frontend:** disable runnable field on route editor from detail panel ([#4469](https://github.com/windmill-labs/windmill/issues/4469)) ([3134f79](https://github.com/windmill-labs/windmill/commit/3134f79ced80aab86912643ab7a60dcf909ab104))
## [1.403.1](https://github.com/windmill-labs/windmill/compare/v1.403.0...v1.403.1) (2024-10-01)

View File

@@ -158,9 +158,6 @@ RUN set -eux; \
ENV PATH="${PATH}:/usr/local/go/bin"
ENV GO_PATH=/usr/local/go/bin/go
# Install UV
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get -y update && apt-get install -y curl nodejs awscli && apt-get clean \
&& rm -rf /var/lib/apt/lists/*

266
backend/Cargo.lock generated
View File

@@ -40,11 +40,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cipher 0.3.0",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher 0.4.4",
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.7.8"
@@ -176,6 +187,19 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "archiver-rs"
version = "0.5.1"
source = "git+https://github.com/gz/archiver-rs.git?branch=patch-1#a73cef92c2a5b8f48c2a4a9e889952072e03b4b7"
dependencies = [
"bzip2",
"flate2",
"tar",
"thiserror",
"xz2",
"zip",
]
[[package]]
name = "argon2"
version = "0.5.3"
@@ -185,7 +209,7 @@ dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
"password-hash 0.5.0",
]
[[package]]
@@ -359,7 +383,7 @@ dependencies = [
"arrow-schema",
"chrono",
"half",
"indexmap 2.6.0",
"indexmap 2.5.0",
"lexical-core",
"num",
"serde",
@@ -486,11 +510,11 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.13"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429"
checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa"
dependencies = [
"brotli 7.0.0",
"brotli",
"bzip2",
"flate2",
"futures-core",
@@ -619,9 +643,9 @@ dependencies = [
[[package]]
name = "async-stream"
version = "0.3.6"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
dependencies = [
"async-stream-impl",
"futures-core",
@@ -630,9 +654,9 @@ dependencies = [
[[package]]
name = "async-stream-impl"
version = "0.3.6"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
@@ -1310,7 +1334,7 @@ dependencies = [
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"constant_time_eq 0.3.1",
]
[[package]]
@@ -1339,7 +1363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
dependencies = [
"block-padding",
"cipher",
"cipher 0.3.0",
]
[[package]]
@@ -1396,17 +1420,6 @@ dependencies = [
"brotli-decompressor",
]
[[package]]
name = "brotli"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "4.0.1"
@@ -1699,6 +1712,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
@@ -1712,9 +1735,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.19"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1722,9 +1745,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.19"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [
"anstream",
"anstyle",
@@ -1875,6 +1898,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
@@ -2230,7 +2259,7 @@ dependencies = [
"arrow-array",
"arrow-ipc",
"arrow-schema",
"async-compression 0.4.13",
"async-compression 0.4.12",
"async-trait",
"bytes",
"bzip2",
@@ -2253,7 +2282,7 @@ dependencies = [
"glob",
"half",
"hashbrown 0.14.5",
"indexmap 2.6.0",
"indexmap 2.5.0",
"itertools 0.12.1",
"log",
"num_cpus",
@@ -2422,7 +2451,7 @@ dependencies = [
"datafusion-expr",
"datafusion-physical-expr",
"hashbrown 0.14.5",
"indexmap 2.6.0",
"indexmap 2.5.0",
"itertools 0.12.1",
"log",
"regex-syntax 0.8.5",
@@ -2451,7 +2480,7 @@ dependencies = [
"half",
"hashbrown 0.14.5",
"hex",
"indexmap 2.6.0",
"indexmap 2.5.0",
"itertools 0.12.1",
"log",
"paste",
@@ -2495,7 +2524,7 @@ dependencies = [
"futures",
"half",
"hashbrown 0.14.5",
"indexmap 2.6.0",
"indexmap 2.5.0",
"itertools 0.12.1",
"log",
"once_cell",
@@ -2900,7 +2929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d"
dependencies = [
"byteorder",
"cipher",
"cipher 0.3.0",
"opaque-debug",
]
@@ -3877,7 +3906,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.6.0",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@@ -3896,7 +3925,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.6.0",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@@ -3945,12 +3974,6 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "hashlink"
version = "0.9.1"
@@ -4356,12 +4379,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
"hashbrown 0.14.5",
"serde",
]
@@ -4384,6 +4407,15 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.13"
@@ -4416,9 +4448,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.10.1"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
[[package]]
name = "is-macro"
@@ -4518,7 +4550,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
]
[[package]]
@@ -4816,7 +4848,7 @@ version = "3.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c42f95f9d296f2dcb50665f507ed5a68a171453142663ce44d77a4eb217b053"
dependencies = [
"aes",
"aes 0.7.5",
"base64 0.21.7",
"block-modes",
"crc-any",
@@ -5663,7 +5695,7 @@ dependencies = [
"arrow-schema",
"arrow-select",
"base64 0.22.1",
"brotli 6.0.0",
"brotli",
"bytes",
"chrono",
"flate2",
@@ -5693,6 +5725,17 @@ dependencies = [
"regex",
]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "password-hash"
version = "0.5.0"
@@ -5716,6 +5759,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest 0.10.7",
"hmac",
"password-hash 0.4.2",
"sha2 0.10.8",
]
[[package]]
name = "pem"
version = "1.1.1"
@@ -5766,7 +5821,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.6.0",
"indexmap 2.5.0",
]
[[package]]
name = "pg-embed"
version = "0.7.2"
source = "git+https://github.com/faokunega/pg-embed#72db5e053f0afac6eee51d3baa2fd5c90803e02d"
dependencies = [
"archiver-rs",
"async-trait",
"bytes",
"dirs",
"futures",
"lazy_static",
"log",
"reqwest 0.11.27",
"thiserror",
"tokio",
"zip",
]
[[package]]
@@ -6698,7 +6771,7 @@ version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
dependencies = [
"async-compression 0.4.13",
"async-compression 0.4.12",
"base64 0.22.1",
"bytes",
"encoding_rs",
@@ -7451,7 +7524,7 @@ version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
"itoa",
"memchr",
"ryu",
@@ -7547,15 +7620,15 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.10.0"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc"
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.6.0",
"indexmap 2.5.0",
"serde",
"serde_derive",
"serde_json",
@@ -7565,9 +7638,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.10.0"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec"
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
dependencies = [
"darling 0.20.10",
"proc-macro2",
@@ -7581,7 +7654,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
"itoa",
"ryu",
"serde",
@@ -7961,7 +8034,7 @@ dependencies = [
"hashbrown 0.14.5",
"hashlink",
"hex",
"indexmap 2.6.0",
"indexmap 2.5.0",
"log",
"memchr",
"once_cell",
@@ -8338,7 +8411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b67e115ab136fe0eb03558bb0508ca7782eeb446a96d165508c48617e3fd94"
dependencies = [
"anyhow",
"indexmap 2.6.0",
"indexmap 2.5.0",
"serde",
"serde_json",
"swc_cached",
@@ -8489,7 +8562,7 @@ checksum = "d37dc505c92af56d0f77cf6f31a6ccd37ac40cad1e01ff77277e0b1c70e8f8ff"
dependencies = [
"better_scoped_tls",
"bitflags 2.6.0",
"indexmap 2.6.0",
"indexmap 2.5.0",
"once_cell",
"phf",
"rustc-hash 1.1.0",
@@ -8558,7 +8631,7 @@ checksum = "446da32cac8299973aaf1d37496562bfd0c1e4f3c3ab5d0af6f07f42e8184102"
dependencies = [
"base64 0.21.7",
"dashmap",
"indexmap 2.6.0",
"indexmap 2.5.0",
"once_cell",
"serde",
"sha1",
@@ -8597,7 +8670,7 @@ version = "0.130.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e62b199454a576c5fdbd7e1bef8ab88a395427456d8a713d994b7d469833aa"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
"num_cpus",
"once_cell",
"rustc-hash 1.1.0",
@@ -9380,7 +9453,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -9393,7 +9466,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -9467,7 +9540,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
"async-compression 0.4.13",
"async-compression 0.4.12",
"bitflags 2.6.0",
"bytes",
"futures-core",
@@ -9852,9 +9925,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.17"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-id"
@@ -10355,7 +10428,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"axum",
@@ -10369,6 +10442,7 @@ dependencies = [
"lazy_static",
"object_store",
"once_cell",
"pg-embed",
"prometheus",
"quote",
"rand 0.8.5",
@@ -10396,7 +10470,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"argon2",
@@ -10480,7 +10554,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"base64 0.21.7",
"chrono",
@@ -10498,7 +10572,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"chrono",
"serde",
@@ -10511,7 +10585,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"async-stream",
@@ -10529,7 +10603,7 @@ dependencies = [
"hex",
"hmac",
"hyper 1.4.1",
"indexmap 2.6.0",
"indexmap 2.5.0",
"itertools 0.13.0",
"lazy_static",
"magic-crypt",
@@ -10556,7 +10630,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"regex",
"rsmq_async",
@@ -10571,7 +10645,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"bytes",
@@ -10592,7 +10666,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -10601,7 +10675,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -10613,7 +10687,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"gosyn",
@@ -10625,7 +10699,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -10637,7 +10711,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10648,7 +10722,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10659,7 +10733,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -10677,7 +10751,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10694,7 +10768,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -10706,7 +10780,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -10724,7 +10798,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -10745,7 +10819,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"serde_json",
@@ -10755,7 +10829,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -10788,7 +10862,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -10798,7 +10872,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.405.3"
version = "1.403.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -10845,7 +10919,6 @@ dependencies = [
"serde_json",
"sha2 0.10.8",
"sqlx",
"swc_ecma_parser 0.144.3",
"tar",
"tiberius",
"tokio",
@@ -11222,9 +11295,18 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes 0.8.4",
"byteorder",
"bzip2",
"constant_time_eq 0.1.5",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd 0.11.2+zstd.1.5.2",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.405.3"
version = "1.403.1"
authors.workspace = true
edition.workspace = true
@@ -27,7 +27,7 @@ members = [
]
[workspace.package]
version = "1.405.3"
version = "1.403.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -47,6 +47,7 @@ stripe = ["windmill-api/stripe"]
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark", "windmill-common/benchmark"]
flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
loki = ["windmill-common/loki"]
pg_embed = ["dep:pg-embed"]
embedding = ["windmill-api/embedding"]
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "windmill-indexer/parquet", "dep:object_store"]
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus"]
@@ -56,7 +57,6 @@ 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"]
deno_core = ["windmill-worker/deno_core", "dep:deno_core"]
[dependencies]
anyhow.workspace = true
@@ -85,8 +85,9 @@ uuid.workspace = true
gethostname.workspace = true
serde_json.workspace = true
serde.workspace = true
deno_core = { workspace = true, optional = true }
deno_core.workspace = true
object_store = { workspace = true, optional = true }
pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true, default-features = false, features = ['rt_tokio']}
quote.workspace = true
@@ -104,7 +105,6 @@ serde.workspace = true
windmill-api-client.workspace = true
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] }
[workspace.dependencies]
windmill-api = { path = "./windmill-api", default-features = false }
windmill-queue = { path = "./windmill-queue" }

View File

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

View File

@@ -1,2 +0,0 @@
-- Add up migration script here
ALTER TYPE SCRIPT_KIND ADD VALUE IF NOT EXISTS 'preprocessor';

View File

@@ -399,11 +399,15 @@ fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
if c > 0 && c <= 6 {
ret.verbosity = Some("v".repeat(c.min(6)));
}
}
}
_ => (),
_ => ()
}
}
}
}
@@ -418,10 +422,10 @@ fn count_consecutive_vs(s: &str) -> usize {
if c == 'v' {
current_count += 1;
if current_count == 6 {
return 6; // Stop early if we reach 6
return 6; // Stop early if we reach 6
}
} else {
current_count = 0; // Reset count if the character is not 'v'
current_count = 0; // Reset count if the character is not 'v'
}
max_count = max_count.max(current_count);
}

View File

@@ -1,16 +0,0 @@
use anyhow::anyhow;
#[cfg(feature = "enterprise")]
use windmill_common::error::{Error, Result};
pub async fn set_license_key(_license_key: String) -> anyhow::Result<()> {
// Implementation is not open source
Err(anyhow!("License cannot be set in Windmill CE"))
}
#[cfg(feature = "enterprise")]
pub async fn verify_license_key() -> Result<()> {
// Implementation is not open source
Err(Error::InternalErr(
"License always invalid in Windmill CE".to_string(),
))
}

1
backend/src/ee.rs Symbolic link
View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/src/ee.rs

View File

@@ -67,7 +67,7 @@ use windmill_worker::{
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
BUN_DEPSTAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR, POWERSHELL_CACHE_DIR,
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR, UV_CACHE_DIR,
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
};
use crate::monitor::{
@@ -92,6 +92,9 @@ const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
mod ee;
mod monitor;
#[cfg(feature = "pg_embed")]
mod pg_embed;
#[inline(always)]
fn create_and_run_current_thread_inner<F, R>(future: F) -> R
where
@@ -115,7 +118,6 @@ where
}
pub fn main() -> anyhow::Result<()> {
#[cfg(feature = "deno_core")]
deno_core::JsRuntime::init_platform(None);
create_and_run_current_thread_inner(windmill_main())
}
@@ -135,7 +137,6 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
})?;
create_dir_all(HUB_CACHE_DIR).await?;
create_dir_all(BUN_BUNDLE_CACHE_DIR).await?;
for path in paths.values() {
tracing::info!("Caching hub script at {path}");
@@ -167,7 +168,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
create_dir_all(&job_dir).await?;
if let Some(lockfile) = res.lockfile {
let _ = windmill_worker::prepare_job_dir(&lockfile, &job_dir).await?;
let envs = windmill_worker::get_common_bun_proc_envs(None).await;
let _ = windmill_worker::install_bun_lockfile(
&mut 0,
&mut None,
@@ -176,31 +177,11 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
None,
&job_dir,
"cache_init",
envs.clone(),
windmill_worker::get_common_bun_proc_envs(None).await,
false,
&mut None,
)
.await?;
let _ = windmill_common::worker::write_file(&job_dir, "main.js", &res.content)?;
if let Err(e) = windmill_worker::prebundle_bun_script(
&res.content,
Some(lockfile),
&path,
&job_id,
"admins",
None,
&job_dir,
"",
"cache_init",
"",
&mut None,
)
.await
{
panic!("Error prebundling bun script: {e:#}");
}
} else {
tracing::warn!("No lockfile found for bun script {path}, skipping...");
}
@@ -360,6 +341,14 @@ async fn windmill_main() -> anyhow::Result<()> {
config
});
#[cfg(feature = "pg_embed")]
let _pg = {
let (db_url, pg) = pg_embed::start().await.expect("pg embed");
tracing::info!("Use embedded pg: {db_url}");
std::env::set_var("DATABASE_URL", db_url);
pg
};
tracing::info!("Connecting to database...");
let db = windmill_common::connect_db(server_mode, indexer_mode).await?;
tracing::info!("Database connected");
@@ -384,16 +373,8 @@ async fn windmill_main() -> anyhow::Result<()> {
let is_agent = mode == Mode::Agent;
if !is_agent {
let skip_migration = std::env::var("SKIP_MIGRATION")
.map(|val| val == "true")
.unwrap_or(false);
if !skip_migration {
// migration code to avoid break
windmill_api::migrate_db(&db).await?;
} else {
tracing::info!("SKIP_MIGRATION set, skipping db migration...")
}
// migration code to avoid break
windmill_api::migrate_db(&db).await?;
}
let (killpill_tx, mut killpill_rx) = tokio::sync::broadcast::channel::<()>(2);
@@ -893,7 +874,6 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
LOCK_CACHE_DIR,
TMP_LOGS_DIR,
PIP_CACHE_DIR,
UV_CACHE_DIR,
TAR_PIP_CACHE_DIR,
DENO_CACHE_DIR,
DENO_CACHE_DIR_DEPS,

46
backend/src/pg_embed.rs Normal file
View File

@@ -0,0 +1,46 @@
use pg_embed::pg_enums::PgAuthMethod;
use pg_embed::pg_fetch::PgFetchSettings;
use pg_embed::postgres::{PgEmbed, PgSettings};
use std::path::PathBuf;
use std::time::Duration;
pub async fn start() -> anyhow::Result<(String, PgEmbed)> {
let pg_settings = PgSettings {
database_dir: PathBuf::from("/tmp/db"),
port: 6543,
user: "postgres".to_string(),
password: "password".to_string(),
auth_method: PgAuthMethod::Plain,
persistent: false,
timeout: Some(Duration::from_secs(15)),
migration_dir: None,
};
let fetch_settings = PgFetchSettings {
version: pg_embed::pg_fetch::PostgresVersion("15.3.0"),
..Default::default()
};
tracing::info!(
"Fetch settings: {:?} {:?}",
fetch_settings.operating_system,
fetch_settings.architecture
);
let mut pg = PgEmbed::new(pg_settings, fetch_settings).await?;
pg.setup().await.expect("pg setup");
pg.start_db().await.expect("pg start db");
//TODO: re-enable this to make it work
// if !pg.database_exists("windmill").await.expect("db exists") {
// pg.create_database("windmill")
// .await
// .expect("pg create database");
// }
let uri = pg.full_db_uri("windmill");
Ok((uri, pg))
}

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.405.3
version: 1.403.1
title: Windmill API
contact:

View File

@@ -1,32 +0,0 @@
use anyhow::anyhow;
#[cfg(feature = "enterprise")]
use std::sync::Arc;
#[cfg(feature = "enterprise")]
use tokio::sync::RwLock;
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<String> {
// Implementation is not open source
Err(anyhow!("License can't be validated in Windmill CE"))
}
#[cfg(feature = "enterprise")]
pub async fn jwt_ext_auth(
_w_id: Option<&String>,
_token: &str,
_external_jwks: Option<Arc<RwLock<ExternalJwks>>>,
) -> anyhow::Result<(crate::db::ApiAuthed, usize)> {
// Implementation is not open source
Err(anyhow!("External JWT auth is not open source"))
}
#[cfg(feature = "enterprise")]
pub struct ExternalJwks;
#[cfg(feature = "enterprise")]
impl ExternalJwks {
pub async fn load() -> Option<Arc<RwLock<Self>>> {
// Implementation is not open source
None
}
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/ee.rs

View File

@@ -1,5 +0,0 @@
use axum::Router;
pub fn workspaced_service() -> Router {
Router::new()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/indexer_ee.rs

View File

@@ -1,5 +0,0 @@
use axum::Router;
pub fn workspaced_service() -> Router {
Router::new()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/job_helpers_ee.rs

View File

@@ -11,6 +11,7 @@ use axum::http::HeaderValue;
use quick_cache::sync::Cache;
use serde_json::value::RawValue;
use sqlx::Pool;
use windmill_common::error::JsonResult;
use std::collections::HashMap;
#[cfg(feature = "prometheus")]
use std::sync::atomic::Ordering;
@@ -18,7 +19,6 @@ use tokio::io::AsyncReadExt;
#[cfg(feature = "prometheus")]
use tokio::time::Instant;
use tower::ServiceBuilder;
use windmill_common::error::JsonResult;
use windmill_common::flow_status::{JobResult, RestartedFrom};
use windmill_common::jobs::{
format_completed_job_result, format_result, CompletedJobWithFormattedResult, FormattedResult,
@@ -293,8 +293,11 @@ pub fn workspace_unauthed_service() -> Router {
pub fn global_root_service() -> Router {
Router::new()
.route("/db_clock", get(get_db_clock))
.route("/completed/count_by_tag", get(count_by_tag))
.route("/db_clock", get(get_db_clock))
.route(
"/completed/count_by_tag",
get(count_by_tag),
)
}
#[derive(Deserialize)]
@@ -4127,7 +4130,7 @@ async fn run_dependencies_job(
JsonRawValue::from_string("true".to_string()).unwrap(),
);
if language == ScriptLang::Bun {
let annotation = windmill_common::worker::get_annotation_ts(&raw_code);
let annotation = windmill_common::worker::get_annotation(&raw_code);
hm.insert(
"npm_mode".to_string(),
JsonRawValue::from_string(annotation.npm_mode.to_string()).unwrap(),
@@ -4680,8 +4683,8 @@ async fn get_job_update(
.fetch_optional(&db)
.await?;
let progress: Option<i32> = if get_progress == Some(true) {
sqlx::query_scalar!(
let progress: Option<i32> = if get_progress == Some(true){
sqlx::query_scalar!(
"SELECT scalar_int FROM job_stats WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
&w_id,
job_id,
@@ -5112,6 +5115,8 @@ async fn get_completed_job_result(
Ok(Json(result).into_response())
}
#[derive(Deserialize)]
struct CountByTagQuery {
horizon_secs: Option<i64>,
@@ -5125,7 +5130,7 @@ struct TagCount {
}
async fn count_by_tag(
ApiAuthed { email, .. }: ApiAuthed,
ApiAuthed { email, ..}: ApiAuthed,
Extension(db): Extension<DB>,
Query(query): Query<CountByTagQuery>,
) -> JsonResult<Vec<TagCount>> {

View File

@@ -1,188 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2022
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use std::{collections::HashMap, fmt::Debug};
use axum::{routing::get, Json, Router};
use hmac::Mac;
use hyper::HeaderMap;
use itertools::Itertools;
use oauth2::{Client as OClient, *};
use serde::{Deserialize, Serialize};
use sqlx::{Postgres, Transaction};
use windmill_common::more_serde::maybe_number_opt;
use crate::OAUTH_CLIENTS;
use windmill_common::error;
use windmill_common::oauth2::*;
use crate::db::DB;
use std::str;
pub fn global_service() -> Router {
Router::new()
.route("/list_supabase", get(list_supabase))
.route("/list_logins", get(list_logins))
.route("/list_connects", get(list_connects))
}
pub fn workspaced_service() -> Router {
Router::new()
}
#[derive(Serialize)]
#[serde(tag = "type")]
pub enum InstanceEvent {
UserAdded { email: String },
// UserDeleted { email: String },
// UserDeletedWorkspace { workspace: String, email: String },
UserAddedWorkspace { workspace: String, email: String },
UserInvitedWorkspace { workspace: String, email: String },
UserJoinedWorkspace { workspace: String, email: String, username: String },
}
#[derive(Debug, Clone)]
pub struct ClientWithScopes {
_client: OClient,
_scopes: Vec<String>,
_extra_params: Option<HashMap<String, String>>,
_extra_params_callback: Option<HashMap<String, String>>,
_allowed_domains: Option<Vec<String>>,
_userinfo_url: Option<String>,
}
pub type BasicClientsMap = HashMap<String, ClientWithScopes>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OAuthConfig {
auth_url: String,
token_url: String,
userinfo_url: Option<String>,
scopes: Option<Vec<String>>,
extra_params: Option<HashMap<String, String>>,
extra_params_callback: Option<HashMap<String, String>>,
req_body_auth: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OAuthClient {
id: String,
secret: String,
allowed_domains: Option<Vec<String>>,
connect_config: Option<OAuthConfig>,
login_config: Option<OAuthConfig>,
}
#[derive(Debug)]
pub struct AllClients {
pub logins: BasicClientsMap,
pub connects: BasicClientsMap,
pub slack: Option<OClient>,
}
pub fn build_oauth_clients(
_base_url: &str,
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
) -> anyhow::Result<AllClients> {
// Implementation is not open source
return Ok(AllClients {
logins: HashMap::default(),
connects: HashMap::default(),
slack: None,
});
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TokenResponse {
access_token: AccessToken,
#[serde(deserialize_with = "maybe_number_opt")]
#[serde(default)]
expires_in: Option<u64>,
refresh_token: Option<RefreshToken>,
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(default)]
scope: Option<Vec<Scope>>,
}
#[derive(Serialize)]
struct Logins {
oauth: Vec<String>,
saml: Option<String>,
}
async fn list_logins() -> error::JsonResult<Logins> {
// Implementation is not open source
return Ok(Json(Logins { oauth: vec![], saml: None }));
}
async fn list_connects() -> error::JsonResult<Vec<String>> {
Ok(Json(
(&OAUTH_CLIENTS.read().await.connects)
.keys()
.map(|x| x.to_owned())
.collect_vec(),
))
}
pub async fn _refresh_token<'c>(
_tx: Transaction<'c, Postgres>,
_path: &str,
_w_id: &str,
_id: i32,
_db: &DB,
) -> error::Result<String> {
// Implementation is not open source
Err(error::Error::BadRequest(
"Not implemented in Windmill's Open Source repository".to_string(),
))
}
async fn list_supabase(_headers: HeaderMap) -> error::Result<String> {
// Implementation is not open source
Err(error::Error::BadRequest(
"Not implemented in Windmill's Open Source repository".to_string(),
))
}
pub async fn check_nb_of_user(db: &DB) -> error::Result<()> {
let nb_users_sso =
sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",)
.fetch_one(db)
.await?;
if nb_users_sso.unwrap_or(0) >= 10 {
return Err(error::Error::BadRequest(
"You have reached the maximum number of oauth users accounts (10) without an enterprise license"
.to_string(),
));
}
let nb_users = sqlx::query_scalar!("SELECT COUNT(*) FROM password",)
.fetch_one(db)
.await?;
if nb_users.unwrap_or(0) >= 50 {
return Err(error::Error::BadRequest(
"You have reached the maximum number of accounts (50) without an enterprise license"
.to_string(),
));
}
return Ok(());
}
#[derive(Clone, Debug)]
pub struct SlackVerifier {
_mac: HmacSha256,
}
impl SlackVerifier {
pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
HmacSha256::new_from_slice(secret.as_ref())
.map(|mac| SlackVerifier { _mac: mac })
.map_err(|_| anyhow::anyhow!("invalid secret"))
}
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/oauth2_ee.rs

View File

@@ -1,17 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2023
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use axum::Router;
pub fn global_service() -> Router {
Router::new()
}
pub fn workspaced_service() -> Router {
Router::new()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/oidc_ee.rs

View File

@@ -58,10 +58,7 @@ pub fn workspaced_service() -> Router {
.route("/type/exists/:name", get(exists_resource_type))
.route("/type/update/:name", post(update_resource_type))
.route("/type/delete/:name", delete(delete_resource_type))
.route(
"/file_resource_type_to_file_ext_map",
get(file_resource_ext_to_resource_type),
)
.route("/file_resource_type_to_file_ext_map", get(file_resource_ext_to_resource_type))
.route("/type/create", post(create_resource_type))
}

View File

@@ -1,25 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2023
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
#![allow(non_snake_case)]
use axum::{routing::post, Router};
pub struct ServiceProviderExt();
pub async fn build_sp_extension() -> anyhow::Result<ServiceProviderExt> {
return Ok(ServiceProviderExt());
}
pub fn global_service() -> Router {
Router::new().route("/acs", post(acs))
}
pub async fn acs() -> String {
// Implementation is not open source as it is a Windmill Enterprise Edition feature
"SAML available only in enterprise version".to_string()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/saml_ee.rs

View File

@@ -1,23 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2023
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use axum::{middleware::Next, response::Response, routing::get, Router};
use hyper::Request;
pub fn global_service() -> Router {
Router::new().route("/ee", get(ee))
}
pub async fn ee() -> String {
return "Enterprise Edition".to_string();
}
pub async fn has_scim_token<B>(_request: Request<B>, _next: Next) -> Response {
//Not implemented in open-source version
todo!()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/scim_ee.rs

View File

@@ -53,7 +53,7 @@ use windmill_common::{
utils::{
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
},
worker::{get_annotation_ts, to_raw_value},
worker::{get_annotation, to_raw_value},
HUB_BASE_URL,
};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
@@ -601,7 +601,7 @@ async fn create_script_internal<'c>(
};
let lang = if &ns.language == &ScriptLang::Bun || &ns.language == &ScriptLang::Bunnative {
let anns = get_annotation_ts(&ns.content);
let anns = get_annotation(&ns.content);
if anns.native_mode {
ScriptLang::Bunnative
} else {

View File

@@ -1,17 +0,0 @@
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"))
}
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/smtp_server_ee.rs

View File

@@ -1,7 +0,0 @@
#[cfg(feature = "stripe")]
use axum::Router;
#[cfg(feature = "stripe")]
pub fn add_stripe_routes(router: Router) -> Router {
return router;
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-api/src/stripe_ee.rs

View File

@@ -1,75 +0,0 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2022
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use std::collections::HashMap;
use windmill_common::{
error::{Error, Result},
utils::Pagination,
};
use crate::{ActionKind, AuditLog, ListAuditLogQuery};
use sqlx::{Postgres, Transaction};
#[derive(Clone)]
pub struct AuditAuthor {
pub username: String,
pub email: String,
pub username_override: Option<String>,
}
impl AuditAuthorable for AuditAuthor {
fn email(&self) -> &str {
&self.email
}
fn username(&self) -> &str {
&self.username
}
fn username_override(&self) -> Option<&str> {
self.username_override.as_deref()
}
}
pub trait AuditAuthorable {
fn username(&self) -> &str;
fn email(&self) -> &str;
fn username_override(&self) -> Option<&str>;
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>(
_db: E,
_author: &impl AuditAuthorable,
mut _operation: &str,
_action_kind: ActionKind,
_w_id: &str,
mut _resource: Option<&str>,
_parameters: Option<HashMap<&str, &str>>,
) -> Result<()> {
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
Ok(())
}
pub async fn list_audit(
_tx: Transaction<'_, Postgres>,
_w_id: String,
_pagination: Pagination,
_lq: ListAuditLogQuery,
) -> Result<Vec<AuditLog>> {
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
return Ok(vec![]);
}
pub async fn get_audit(tx: Transaction<'_, Postgres>, _id: i32, _w_id: &str) -> Result<AuditLog> {
// Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature
tx.commit().await?;
Err(Error::NotFound(
"Audit log not not available in Windmill Community edition".to_string(),
))
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-audit/src/audit_ee.rs

View File

@@ -1,83 +0,0 @@
#[cfg(feature = "enterprise")]
use crate::db::DB;
use crate::ee::LicensePlan::Community;
#[cfg(feature = "enterprise")]
use crate::error;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::RwLock;
lazy_static::lazy_static! {
pub static ref LICENSE_KEY_VALID: Arc<RwLock<bool>> = Arc::new(RwLock::new(true));
pub static ref LICENSE_KEY_ID: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
pub static ref LICENSE_KEY: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
}
pub enum LicensePlan {
Community,
Pro,
Enterprise,
}
pub async fn get_license_plan() -> LicensePlan {
// Implementation is not open source
return Community;
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum CriticalErrorChannel {
Email { email: String },
Slack { slack_channel: String },
}
pub enum CriticalAlertKind {
#[cfg(feature = "enterprise")]
CriticalError,
#[cfg(feature = "enterprise")]
RecoveredCriticalError,
}
#[cfg(feature = "enterprise")]
pub async fn send_critical_alert(
_error_message: String,
_db: &DB,
_kind: CriticalAlertKind,
_channels: Option<Vec<CriticalErrorChannel>>,
) {
}
#[cfg(feature = "enterprise")]
pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () {
// Implementation is not open source
}
#[cfg(feature = "enterprise")]
pub enum RenewReason {
Manual,
Schedule,
OnStart,
}
#[cfg(feature = "enterprise")]
pub async fn renew_license_key(
_http_client: &reqwest::Client,
_db: &crate::db::DB,
_key: Option<String>,
_reason: RenewReason,
) -> String {
// Implementation is not open source
"".to_string()
}
#[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())
}
#[cfg(feature = "enterprise")]
pub async fn worker_groups_alerts(_db: &DB) {}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-common/src/ee.rs

View File

@@ -1,18 +0,0 @@
use std::future::Future;
use crate::{
error::Error,
s3_helpers::{ObjectStoreResource, StorageResourceType},
};
pub async fn get_s3_resource_internal<'c, F, Fut>(
_resource_type: StorageResourceType,
_s3_resource_value_raw: serde_json::Value,
_gen_token: F,
) -> crate::error::Result<ObjectStoreResource>
where
F: FnOnce(String) -> Fut,
Fut: Future<Output = Result<String, Error>> + Send + 'static,
{
todo!()
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-common/src/job_s3_helpers_ee.rs

View File

@@ -1,48 +0,0 @@
use sqlx::Postgres;
use crate::{error::Result, scripts::ScriptLang, DB};
pub async fn get_disable_stats_setting(_db: &DB) -> bool {
// stats details are closed source
false
}
pub async fn schedule_stats(_db: &DB, _http_client: &reqwest::Client) -> () {
// stats details are closed source
}
#[derive(Debug, sqlx::FromRow, serde::Serialize)]
struct JobsUsage {
language: Option<ScriptLang>,
total_duration: i64,
count: i64,
}
pub enum SendStatsReason {
Manual,
Schedule,
OnStart,
}
pub async fn send_stats(
_http_client: &reqwest::Client,
_db: &DB,
_skip_job_usage: bool,
_reason: SendStatsReason,
) -> Result<()> {
// stats details are closed source
Ok(())
}
pub struct ActiveUserUsage {
pub author_count: Option<i32>,
pub operator_count: Option<i32>,
}
pub async fn get_user_usage<'c, E: sqlx::Executor<'c, Database = Postgres>>(
_db: E,
) -> Result<ActiveUserUsage> {
let usage = ActiveUserUsage { author_count: None, operator_count: None };
Ok(usage)
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-common/src/stats_ee.rs

View File

@@ -303,14 +303,14 @@ fn parse_file<T: FromStr>(path: &str) -> Option<T> {
.flatten()
}
pub struct TypeScriptAnnotations {
pub struct Annotations {
pub npm_mode: bool,
pub nodejs_mode: bool,
pub native_mode: bool,
pub nobundling: bool,
}
pub fn get_annotation_ts(inner_content: &str) -> TypeScriptAnnotations {
pub fn get_annotation(inner_content: &str) -> Annotations {
let annotations = inner_content
.lines()
.take_while(|x| x.starts_with("//"))
@@ -324,25 +324,7 @@ pub fn get_annotation_ts(inner_content: &str) -> TypeScriptAnnotations {
let nobundling: bool =
annotations.contains(&"nobundling".to_string()) || nodejs_mode || *DISABLE_BUNDLING;
TypeScriptAnnotations { npm_mode, nodejs_mode, native_mode, nobundling }
}
pub struct PythonAnnotations {
pub no_uv: bool,
pub no_cache: bool,
}
pub fn get_annotation_python(inner_content: &str) -> PythonAnnotations {
let annotations = inner_content
.lines()
.take_while(|x| x.starts_with("#"))
.map(|x| x.to_string().replace("#", "").trim().to_string())
.collect_vec();
let no_uv: bool = annotations.contains(&"no_uv".to_string());
let no_cache: bool = annotations.contains(&"no_cache".to_string());
PythonAnnotations { no_uv, no_cache }
Annotations { npm_mode, nodejs_mode, native_mode, nobundling }
}
pub struct SqlAnnotations {
@@ -467,13 +449,10 @@ pub async fn save_cache(
fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> {
use std::fs::{File, Permissions};
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
let mut file = File::create(main_path)?;
file.write_all(byts)?;
#[cfg(unix)]
file.set_permissions(Permissions::from_mode(0o755))?;
file.flush()?;
Ok(())

View File

@@ -1,17 +0,0 @@
use windmill_common::error::Result;
use crate::{DeployedObject, DB};
pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + Clone + 'c>(
_email: &str,
_created_by: &str,
_db: &DB,
_w_id: &str,
_obj: DeployedObject,
_deployment_message: Option<String>,
_rsmq: Option<R>,
_skip_db_insert: bool,
) -> Result<()> {
// Git sync is an enterprise feature and not part of the open-source version
return Ok(());
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-git-sync/src/git_sync_ee.rs

View File

@@ -1,21 +0,0 @@
use anyhow::anyhow;
use sqlx::{Pool, Postgres};
use windmill_common::error::Error;
#[derive(Clone)]
pub struct IndexReader;
#[derive(Clone)]
pub struct IndexWriter;
pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> {
Err(anyhow!("Cannot initialize index: not in EE").into())
}
pub async fn run_indexer(
_db: Pool<Postgres>,
mut _index_writer: IndexWriter,
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
) {
tracing::error!("Cannot run indexer: not in EE");
}

View File

@@ -0,0 +1 @@
/git/windmill/../windmill-ee-private/windmill-indexer/src/indexer_ee.rs

View File

@@ -18,7 +18,6 @@ parquet = ["windmill-common/parquet", "dep:object_store"]
flow_testing = []
cloud = []
sqlx = []
deno_core = ["dep:deno_fetch", "dep:deno_webidl", "dep:deno_web", "dep:deno_net", "dep:deno_console", "dep:deno_url", "dep:deno_core", "dep:deno_ast", "dep:deno_tls"]
[dependencies]
windmill-queue.workspace = true
@@ -60,15 +59,15 @@ once_cell.workspace = true
rsmq_async.workspace = true
tokio-postgres.workspace = true
bit-vec.workspace = true
deno_fetch = { workspace = true, optional = true }
deno_webidl = { workspace = true, optional = true }
deno_web = { workspace = true, optional = true }
deno_net = { workspace = true, optional = true }
deno_console = { workspace = true, optional = true }
deno_url = { workspace = true, optional = true }
deno_core = { workspace = true, optional = true }
deno_ast = { workspace = true, optional = true }
deno_tls = { workspace = true, optional = true }
deno_fetch.workspace = true
deno_webidl.workspace = true
deno_web.workspace = true
deno_net.workspace = true
deno_console.workspace = true
deno_url.workspace = true
deno_core.workspace = true
deno_ast.workspace = true
deno_tls.workspace = true
postgres-native-tls.workspace = true
native-tls.workspace = true
mysql_async.workspace = true
@@ -90,17 +89,13 @@ tar.workspace = true
object_store = { workspace = true, optional = true}
convert_case.workspace = true
yaml-rust.workspace = true
swc_ecma_parser.workspace = true
[build-dependencies]
deno_fetch = { workspace = true, optional = true }
deno_webidl = { workspace = true, optional = true }
deno_web = { workspace = true, optional = true }
deno_net = { workspace = true, optional = true }
deno_console = { workspace = true, optional = true }
deno_url = { workspace = true, optional = true }
deno_core = { workspace = true, optional = true }
deno_ast = { workspace = true, optional = true }
deno_tls = { workspace = true, optional = true }
deno_fetch.workspace = true
deno_webidl.workspace = true
deno_web.workspace = true
deno_console.workspace = true
deno_url.workspace = true
deno_core.workspace = true
deno_net.workspace = true
zstd.workspace = true

View File

@@ -1,22 +1,13 @@
#[cfg(feature = "deno_core")]
use deno_fetch::FetchPermissions;
#[cfg(feature = "deno_core")]
use deno_net::NetPermissions;
#[cfg(feature = "deno_core")]
use deno_web::{BlobStore, TimersPermission};
#[cfg(feature = "deno_core")]
use std::env;
#[cfg(feature = "deno_core")]
use std::io::Write;
#[cfg(feature = "deno_core")]
use std::path::PathBuf;
#[cfg(feature = "deno_core")]
use std::sync::Arc;
#[cfg(feature = "deno_core")]
pub struct PermissionsContainer;
#[cfg(feature = "deno_core")]
impl FetchPermissions for PermissionsContainer {
#[inline(always)]
fn check_net_url(
@@ -37,7 +28,6 @@ impl FetchPermissions for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
impl TimersPermission for PermissionsContainer {
#[inline(always)]
fn allow_hrtime(&mut self) -> bool {
@@ -45,7 +35,6 @@ impl TimersPermission for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
impl NetPermissions for PermissionsContainer {
fn check_read(
&mut self,
@@ -72,14 +61,12 @@ impl NetPermissions for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
deno_core::extension!(
fetch,
esm_entry_point = "ext:fetch/src/runtime.js",
esm = ["src/runtime.js"],
);
#[cfg(feature = "deno_core")]
fn main() {
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
@@ -135,6 +122,3 @@ fn main() {
println!("cargo:rerun-if-changed={}", path.display());
}
}
#[cfg(not(feature = "deno_core"))]
fn main() {}

View File

@@ -1,4 +1,3 @@
#[cfg(unix)]
use std::{
collections::HashMap,
os::unix::fs::PermissionsExt,
@@ -6,13 +5,6 @@ use std::{
process::Stdio,
};
#[cfg(windows)]
use std::{
collections::HashMap,
path::{Path, PathBuf},
process::Stdio,
};
use anyhow::anyhow;
use itertools::Itertools;
use serde_json::value::RawValue;
@@ -33,7 +25,7 @@ use crate::{
OccupancyMetrics,
},
handle_child::handle_child,
python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile},
python_executor::{create_dependencies_dir, handle_python_reqs, pip_compile},
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
TZ_ENV,
};
@@ -80,7 +72,7 @@ async fn handle_ansible_python_deps(
if requirements.is_empty() {
"".to_string()
} else {
uv_pip_compile(
pip_compile(
job_id,
&requirements,
mem_peak,
@@ -90,8 +82,6 @@ async fn handle_ansible_python_deps(
worker_name,
w_id,
&mut Some(occupancy_metrics),
false,
false,
)
.await
.map_err(|e| {
@@ -388,7 +378,6 @@ fi
let file = write_file(job_dir, "wrapper.sh", &wrapper)?;
#[cfg(unix)]
file.metadata()?.permissions().set_mode(0o777);
// let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());

View File

@@ -32,9 +32,6 @@ use crate::{
POWERSHELL_CACHE_DIR, POWERSHELL_PATH, TZ_ENV,
};
#[cfg(windows)]
use crate::SYSTEM_ROOT;
lazy_static::lazy_static! {
pub static ref ANSI_ESCAPE_RE: Regex = Regex::new(r"\x1b\[[0-9;]*m").unwrap();
@@ -229,19 +226,13 @@ pub async fn handle_powershell_job(
.collect::<Vec<_>>()
};
#[cfg(windows)]
let split_char = '\\';
#[cfg(unix)]
let split_char = '/';
let installed_modules = fs::read_dir(POWERSHELL_CACHE_DIR)?
.filter_map(|x| {
x.ok().map(|x| {
x.path()
.display()
.to_string()
.split(split_char)
.split('/')
.last()
.unwrap_or_default()
.to_lowercase()
@@ -298,26 +289,14 @@ pub async fn handle_powershell_job(
append_logs(&job.id, &job.workspace_id, logs2, db).await;
// make sure default (only allhostsallusers) modules are loaded, disable autoload (cache can be large to explore especially on cloud) and add /tmp/windmill/cache to PSModulePath
#[cfg(unix)]
let profile = format!(
"$PSModuleAutoloadingPreference = 'None'
$PSModulePathBackup = $env:PSModulePath
$env:PSModulePath = \"$PSHome/Modules\"
$env:PSModulePath = ($Env:PSModulePath -split ':')[-1]
Get-Module -ListAvailable | Import-Module
$env:PSModulePath = \"{}:$PSModulePathBackup\"",
POWERSHELL_CACHE_DIR
);
#[cfg(windows)]
let profile = format!(
"$PSModuleAutoloadingPreference = 'None'
$PSModulePathBackup = $env:PSModulePath
$env:PSModulePath = \"C:\\Program Files\\PowerShell\\7\\Modules\"
Get-Module -ListAvailable | Import-Module
$env:PSModulePath = \"{};$PSModulePathBackup\"",
POWERSHELL_CACHE_DIR
);
// make sure param() is first
let param_match = windmill_parser_bash::RE_POWERSHELL_PARAM.find(&content);
let content: String = if let Some(param_match) = param_match {
@@ -333,29 +312,11 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
};
write_file(job_dir, "main.ps1", content.as_str())?;
#[cfg(unix)]
write_file(
job_dir,
"wrapper.sh",
&format!("set -o pipefail\nset -e\nmkfifo bp\ncat bp | tail -1 > ./result2.out &\n{} -F ./main.ps1 \"$@\" 2>&1 | tee bp\nwait $!", POWERSHELL_PATH.as_str()),
)?;
#[cfg(windows)]
write_file(
job_dir,
"wrapper.ps1",
&format!(
"param([string[]]$args)\n\
$ErrorActionPreference = 'Stop'\n\
$pipe = New-TemporaryFile\n\
& \"{}\" -File ./main.ps1 @args 2>&1 | Tee-Object -FilePath $pipe\n\
Get-Content -Path $pipe | Select-Object -Last 1 | Set-Content -Path './result2.out'\n\
Remove-Item $pipe\n",
POWERSHELL_PATH.as_str()
),
)?;
let token = client.get_token().await;
let mut reserved_variables = get_reserved_variables(job, &token, db).await?;
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
@@ -394,24 +355,10 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
.stderr(Stdio::piped())
.spawn()?
} else {
let mut cmd;
let mut cmd_args;
#[cfg(unix)]
{
cmd_args = vec!["wrapper.sh"];
cmd_args.extend(pwsh_args.iter().map(|x| x.as_str()));
cmd = Command::new(BIN_BASH.as_str());
}
#[cfg(windows)]
{
cmd_args = vec![r".\wrapper.ps1".to_string()];
cmd_args.extend(pwsh_args.iter().map(|x| x.replace("--", "-")));
cmd = Command::new(POWERSHELL_PATH.as_str());
}
cmd.current_dir(job_dir)
let mut cmd_args = vec!["wrapper.sh"];
cmd_args.extend(pwsh_args.iter().map(|x| x.as_str()));
Command::new(BIN_BASH.as_str())
.current_dir(job_dir)
.env_clear()
.envs(envs)
.envs(reserved_variables)
@@ -419,53 +366,11 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
.env("PATH", PATH_ENV.as_str())
.env("BASE_INTERNAL_URL", base_internal_url)
.env("HOME", HOME_ENV.as_str())
.args(&cmd_args)
.args(cmd_args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
cmd.env("SystemRoot", SYSTEM_ROOT.as_str())
.env(
"LOCALAPPDATA",
std::env::var("LOCALAPPDATA")
.unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())),
)
.env(
"ProgramData",
std::env::var("ProgramData")
.unwrap_or_else(|_| String::from("C:\\ProgramData")),
)
.env(
"ProgramFiles",
std::env::var("ProgramFiles")
.unwrap_or_else(|_| String::from("C:\\Program Files")),
)
.env(
"ProgramFiles(x86)",
std::env::var("ProgramFiles(x86)")
.unwrap_or_else(|_| String::from("C:\\Program Files (x86)")),
)
.env(
"ProgramW6432",
std::env::var("ProgramW6432")
.unwrap_or_else(|_| String::from("C:\\Program Files")),
)
.env(
"TMP",
std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")),
)
.env(
"PATHEXT",
std::env::var("PATHEXT").unwrap_or_else(|_| {
String::from(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL")
}),
);
}
cmd.spawn()?
.stderr(Stdio::piped())
.spawn()?
};
handle_child(
&job.id,
db,

View File

@@ -1,14 +1,8 @@
#[cfg(feature = "deno_core")]
use std::time::Instant;
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;
#[cfg(not(feature = "deno_core"))]
use serde_json::value::to_raw_value;
use serde_json::value::RawValue;
use sha2::Digest;
use uuid::Uuid;
use windmill_parser_ts::remove_pinned_imports;
@@ -29,9 +23,6 @@ use crate::{
NODE_PATH, NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, PATH_ENV, TZ_ENV,
};
#[cfg(windows)]
use crate::SYSTEM_ROOT;
use tokio::{fs::File, process::Command};
use tokio::io::AsyncReadExt;
@@ -47,7 +38,7 @@ use windmill_common::{
get_latest_hash_for_path,
jobs::{QueuedJob, PREPROCESSOR_FAKE_ENTRYPOINT},
scripts::ScriptLang,
worker::{exists_in_cache, get_annotation_ts, save_cache, write_file},
worker::{exists_in_cache, get_annotation, save_cache, write_file},
DB,
};
@@ -121,9 +112,6 @@ pub async fn gen_bun_lockfile(
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
child_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
let mut child_process = start_child_process(child_cmd, &*BUN_PATH).await?;
if let Some(db) = db {
@@ -257,9 +245,6 @@ pub async fn install_bun_lockfile(
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
child_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
let mut npm_logs = if npm_mode {
"NPM mode\n".to_string()
} else {
@@ -468,10 +453,6 @@ pub async fn generate_wrapper_mjs(
.args(vec!["run", "node_builder.ts"])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
child.env("SystemRoot", SYSTEM_ROOT.as_str());
let child_process = start_child_process(child, &*BUN_PATH).await?;
handle_child(
job_id,
@@ -506,7 +487,7 @@ pub async fn generate_bun_bundle(
mem_peak: &mut i32,
canceled_by: &mut Option<CanceledBy>,
common_bun_proc_envs: &HashMap<String, String>,
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
occupancy_metrics: &mut OccupancyMetrics,
) -> Result<()> {
let mut child = Command::new(&*BUN_PATH);
child
@@ -517,10 +498,6 @@ pub async fn generate_bun_bundle(
.args(vec!["run", "node_builder.ts"])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
child.env("SystemRoot", SYSTEM_ROOT.as_str());
let mut child_process = start_child_process(child, &*BUN_PATH).await?;
if let Some(db) = db {
handle_child(
@@ -535,7 +512,7 @@ pub async fn generate_bun_bundle(
"bun build",
timeout,
false,
occupancy_metrics,
&mut Some(occupancy_metrics),
)
.await?;
} else {
@@ -563,11 +540,7 @@ pub async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<()> {
if is_tar {
extract_tar(fs::read(bun_cache_path)?.into(), job_dir).await?;
} else {
#[cfg(unix)]
tokio::fs::symlink(&bun_cache_path, dst).await?;
#[cfg(windows)]
std::os::windows::fs::symlink_dir(&bun_cache_path, &dst)?;
}
} else if let Some(os) = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
.read()
@@ -580,11 +553,7 @@ pub async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<()> {
if is_tar {
extract_tar(bytes, job_dir).await?;
} else {
#[cfg(unix)]
tokio::fs::symlink(bun_cache_path, dst).await?;
#[cfg(windows)]
std::os::windows::fs::symlink_dir(&bun_cache_path, &dst)?;
}
// extract_tar(bytes, job_dir).await?;
@@ -650,7 +619,7 @@ pub async fn prebundle_bun_script(
base_internal_url: &str,
worker_name: &str,
token: &str,
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
occupancy_metrics: &mut OccupancyMetrics,
) -> Result<()> {
let (local_path, remote_path) = compute_bundle_local_and_remote_path(
inner_content,
@@ -663,7 +632,7 @@ pub async fn prebundle_bun_script(
if exists_in_cache(&local_path, &remote_path).await {
return Ok(());
}
let annotation = get_annotation_ts(inner_content);
let annotation = get_annotation(inner_content);
if annotation.nobundling {
return Ok(());
}
@@ -753,10 +722,6 @@ async fn compute_bundle_local_and_remote_path(
let hash = windmill_common::utils::calculate_hash(&input_src);
let local_path = format!("{BUN_BUNDLE_CACHE_DIR}/{hash}");
#[cfg(windows)]
let local_path = local_path.replace("/tmp", r"C:\tmp").replace("/", r"\");
let remote_path = format!("{BUN_BUNDLE_OBJECT_STORE_PREFIX}{hash}");
(local_path, remote_path)
}
@@ -800,7 +765,7 @@ pub async fn handle_bun_job(
new_args: &mut Option<HashMap<String, Box<RawValue>>>,
occupancy_metrics: &mut OccupancyMetrics,
) -> error::Result<Box<RawValue>> {
let mut annotation = windmill_common::worker::get_annotation_ts(inner_content);
let mut annotation = windmill_common::worker::get_annotation(inner_content);
let (mut has_bundle_cache, cache_logs, local_path, remote_path) =
if requirements_o.is_some() && !annotation.nobundling && codebase.is_none() {
@@ -850,23 +815,10 @@ pub async fn handle_bun_job(
));
}
let mut gbuntar_name: Option<String> = None;
let mut gbuntar_name = None;
if has_bundle_cache {
let target;
let symlink;
#[cfg(unix)]
{
target = format!("{job_dir}/main.js");
symlink = std::os::unix::fs::symlink(&local_path, &target);
}
#[cfg(windows)]
{
target = format!("{job_dir}\\main.js");
symlink = std::os::windows::fs::symlink_dir(&local_path, &target);
}
symlink.map_err(|e| {
let target = format!("{job_dir}/main.js");
std::os::unix::fs::symlink(&local_path, &target).map_err(|e| {
error::Error::ExecutionErr(format!(
"could not copy cached binary from {local_path} to {job_dir}/main: {e:?}"
))
@@ -1190,7 +1142,7 @@ try {{
mem_peak,
canceled_by,
&common_bun_proc_envs,
&mut Some(occupancy_metrics),
occupancy_metrics,
)
.await?;
if !local_path.is_empty() {
@@ -1238,58 +1190,51 @@ try {{
}
}
if annotation.native_mode {
#[cfg(not(feature = "deno_core"))]
return Ok(to_raw_value("").unwrap());
#[cfg(feature = "deno_core")]
{
let env_code = format!(
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 args = crate::common::build_args_map(job, client, db)
.await?
.map(sqlx::types::Json);
let job_args = if args.is_some() {
args.as_ref()
} else {
job.args.as_ref()
};
let result = crate::js_eval::eval_fetch_timeout(
env_code,
inner_content.clone(),
js_code,
job_args,
job.id,
job.timeout,
db,
mem_peak,
canceled_by,
worker_name,
&job.workspace_id,
false,
occupancy_metrics,
)
.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);
}
let js_code = read_file_content(&format!("{job_dir}/main.js")).await?;
let started_at = Instant::now();
let args = crate::common::build_args_map(job, client, db)
.await?
.map(sqlx::types::Json);
let job_args = if args.is_some() {
args.as_ref()
} else {
job.args.as_ref()
};
let result = crate::js_eval::eval_fetch_timeout(
env_code,
inner_content.clone(),
js_code,
job_args,
job.id,
job.timeout,
db,
mem_peak,
canceled_by,
worker_name,
&job.workspace_id,
false,
occupancy_metrics,
)
.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;
@@ -1381,10 +1326,6 @@ try {{
.args(vec!["--preserve-symlinks", &script_path])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
bun_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
bun_cmd
} else {
let script_path = format!("{job_dir}/wrapper.mjs");
@@ -1411,13 +1352,8 @@ try {{
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
bun_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
bun_cmd
};
start_child_process(
cmd,
if annotation.nodejs_mode {
@@ -1525,7 +1461,7 @@ pub async fn start_worker(
let common_bun_proc_envs: HashMap<String, String> =
get_common_bun_proc_envs(Some(&base_internal_url)).await;
let mut annotation = windmill_common::worker::get_annotation_ts(inner_content);
let mut annotation = windmill_common::worker::get_annotation(inner_content);
//TODO: remove this when bun dedicated workers work without issues
annotation.nodejs_mode = true;

View File

@@ -225,12 +225,7 @@ func Run(req Req) (interface{{}}, error){{
}
} else {
let target = format!("{job_dir}/main");
#[cfg(unix)]
let symlink = std::os::unix::fs::symlink(&bin_path, &target);
#[cfg(windows)]
let symlink = std::os::windows::fs::symlink_dir(&bin_path, &target);
symlink.map_err(|e| {
std::os::unix::fs::symlink(&bin_path, &target).map_err(|e| {
Error::ExecutionErr(format!(
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
))

View File

@@ -6,11 +6,7 @@ use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
use sqlx::{Pool, Postgres};
#[cfg(windows)]
use std::process::Stdio;
use tokio::fs::File;
#[cfg(windows)]
use tokio::process::Command;
use windmill_common::error::to_anyhow;
use windmill_common::error::{self, Error};
@@ -53,33 +49,6 @@ use crate::{MAX_RESULT_SIZE, MAX_WAIT_FOR_SIGINT, MAX_WAIT_FOR_SIGTERM};
lazy_static::lazy_static! {
pub static ref SLOW_LOGS: bool = std::env::var("SLOW_LOGS").ok().is_some_and(|x| x == "1" || x == "true");
}
// - kill windows process along with all child processes
#[cfg(windows)]
async fn kill_process_tree(pid: Option<u32>) -> Result<(), String> {
let pid = match pid {
Some(pid) => pid,
None => return Err("No PID provided to kill.".to_string()),
};
let output = Command::new("cmd")
.args(&["/C", "taskkill", "/PID", &pid.to_string(), "/T", "/F"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await
.map_err(|e| format!("Failed to execute taskkill: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(format!(
"Failed to kill process tree. Error: {}",
String::from_utf8_lossy(&output.stderr)
))
}
}
/// - wait until child exits and return with exit status
/// - read lines from stdout and stderr and append them to the "queue"."logs"
/// quitting early if output exceedes MAX_LOG_SIZE characters (not bytes)
@@ -227,26 +196,9 @@ pub async fn handle_child(
}
}
}
#[cfg(windows)]
{
let pid_to_kill = child.id();
match kill_process_tree(pid_to_kill).await {
Ok(_) => tracing::debug!(
"successfully killed process tree with PID: {:?}",
pid_to_kill
),
Err(e) => tracing::error!("failed to kill process tree: {:?}", e),
};
set_reason.await;
return Ok(Err(kill_reason));
}
#[cfg(unix)]
{
/* send SIGKILL and reap child process */
let (_, kill) = future::join(set_reason, child.kill()).await;
kill.map(|()| Err(kill_reason))
}
/* send SIGKILL and reap child process */
let (_, kill) = future::join(set_reason, child.kill()).await;
kill.map(|()| Err(kill_reason))
};
/* a future that reads output from the child and appends to the database */

View File

@@ -1,5 +1,5 @@
use deno_ast::swc::parser::lexer::util::CharExt;
use itertools::Itertools;
use swc_ecma_parser::lexer::util::CharExt;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
use object_store::path::Path;

View File

@@ -6,72 +6,56 @@
* LICENSE-AGPL for a copy of the license.
*/
#[cfg(feature = "deno_core")]
use std::{
cell::RefCell,
collections::HashMap,
env,
io::{self, BufReader},
rc::Rc,
sync::Arc,
};
use std::{collections::HashMap, sync::Arc};
#[cfg(feature = "deno_core")]
use deno_ast::ParseParams;
#[cfg(feature = "deno_core")]
use deno_core::{
error::AnyError,
op2, serde_v8, url,
v8::{self, IsolateHandle},
Extension, JsRuntime, OpState, PollEventLoopOptions, RuntimeOptions,
};
#[cfg(feature = "deno_core")]
use deno_fetch::FetchPermissions;
#[cfg(feature = "deno_core")]
use deno_net::NetPermissions;
#[cfg(feature = "deno_core")]
use deno_tls::{rustls::RootCertStore, rustls_pemfile};
#[cfg(feature = "deno_core")]
use deno_web::{BlobStore, TimersPermission};
#[cfg(feature = "deno_core")]
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use serde_json::value::RawValue;
use sqlx::types::Json;
#[cfg(feature = "deno_core")]
use tokio::{
sync::{mpsc, oneshot},
time::timeout,
};
use uuid::Uuid;
#[cfg(feature = "deno_core")]
use windmill_common::error::Error;
use windmill_common::{flow_status::JobResult, DB};
use windmill_common::{error::Error, flow_status::JobResult, DB};
use windmill_queue::CanceledBy;
use crate::{common::OccupancyMetrics, AuthedClient};
#[cfg(feature = "deno_core")]
use crate::{common::unsafe_raw, handle_child::run_future_with_polling_update_job_poller};
use crate::{
common::{unsafe_raw, OccupancyMetrics},
handle_child::run_future_with_polling_update_job_poller,
AuthedClient,
};
#[derive(Debug, Clone)]
pub struct IdContext {
pub flow_job: Uuid,
#[allow(dead_code)]
pub steps_results: HashMap<String, JobResult>,
pub previous_id: String,
}
#[cfg(feature = "deno_core")]
pub struct ContainerRootCertStoreProvider {
root_cert_store: RootCertStore,
}
#[cfg(feature = "deno_core")]
impl ContainerRootCertStoreProvider {
fn new() -> ContainerRootCertStoreProvider {
return ContainerRootCertStoreProvider {
@@ -89,17 +73,14 @@ impl ContainerRootCertStoreProvider {
}
}
#[cfg(feature = "deno_core")]
impl deno_tls::RootCertStoreProvider for ContainerRootCertStoreProvider {
fn get_or_try_init(&self) -> Result<&RootCertStore, AnyError> {
Ok(&self.root_cert_store)
}
}
#[cfg(feature = "deno_core")]
pub struct PermissionsContainer;
#[cfg(feature = "deno_core")]
impl FetchPermissions for PermissionsContainer {
#[inline(always)]
fn check_net_url(
@@ -120,7 +101,6 @@ impl FetchPermissions for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
impl TimersPermission for PermissionsContainer {
#[inline(always)]
fn allow_hrtime(&mut self) -> bool {
@@ -128,7 +108,6 @@ impl TimersPermission for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
impl NetPermissions for PermissionsContainer {
fn check_read(
&mut self,
@@ -155,7 +134,6 @@ impl NetPermissions for PermissionsContainer {
}
}
#[cfg(feature = "deno_core")]
pub struct OptAuthedClient(Option<AuthedClient>);
pub async fn eval_timeout(
@@ -164,7 +142,7 @@ pub async fn eval_timeout(
flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
authed_client: Option<&AuthedClient>,
by_id: Option<IdContext>,
#[allow(unused_variables)] ctx: Option<Vec<(String, String)>>,
ctx: Option<Vec<(String, String)>>,
) -> anyhow::Result<Box<RawValue>> {
let expr = expr.trim().to_string();
@@ -234,133 +212,121 @@ pub async fn eval_timeout(
}
}
#[cfg(not(feature = "deno_core"))]
{
#[allow(unreachable_code)]
return todo!();
}
let expr2 = expr.clone();
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
let has_client = authed_client.is_some();
let authed_client = authed_client.cloned();
timeout(
std::time::Duration::from_millis(10000),
tokio::task::spawn_blocking(move || {
let mut ops = vec![op_get_context()];
#[cfg(feature = "deno_core")]
{
let expr2 = expr.clone();
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
let has_client = authed_client.is_some();
let authed_client = authed_client.cloned();
return timeout(
std::time::Duration::from_millis(10000),
tokio::task::spawn_blocking(move || {
let mut ops = vec![op_get_context()];
if authed_client.is_some() {
ops.extend([
// An op for summing an array of numbers
// The op-layer automatically deserializes inputs
// and serializes the returned Result & value
op_variable(),
op_resource(),
])
}
if authed_client.is_some() {
ops.extend([
// An op for summing an array of numbers
// The op-layer automatically deserializes inputs
// and serializes the returned Result & value
op_variable(),
op_resource(),
])
}
if by_id.is_some() && authed_client.is_some() {
ops.push(op_get_result());
ops.push(op_get_id());
}
if by_id.is_some() && authed_client.is_some() {
ops.push(op_get_result());
ops.push(op_get_id());
}
let ext = Extension { name: "js_eval", ops: ops.into(), ..Default::default() };
let exts = vec![ext];
// Use our snapshot to provision our new runtime
let options = RuntimeOptions {
extensions: exts,
// startup_snapshot: Some(Snapshot::Static(buffer)),
..Default::default()
};
let mut context_keys = transform_context
.keys()
.filter(|x| expr.contains(&x.to_string()))
.map(|x| x.clone())
.collect_vec();
if !context_keys.contains(&"previous_result".to_string())
&& (p_ids.is_some() && p_ids.as_ref().unwrap().iter().any(|x| expr.contains(x)))
|| expr.contains("error")
{
// tracing::error!("PREVIOUS_RESULT");
context_keys.push("previous_result".to_string());
}
let has_flow_input = expr.contains("flow_input");
if has_flow_input {
context_keys.push("flow_input".to_string())
}
let mut js_runtime = JsRuntime::new(options);
{
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
let mut client = authed_client.clone();
if let Some(client) = client.as_mut() {
client.force_client = Some(
reqwest::ClientBuilder::new()
.user_agent("windmill/beta")
.danger_accept_invalid_certs(
std::env::var("ACCEPT_INVALID_CERTS").is_ok(),
)
.build()
.unwrap(),
);
}
op_state.put(OptAuthedClient(client));
op_state.put(TransformContext {
flow_input: if has_flow_input { flow_input } else { None },
envs: transform_context
.into_iter()
.filter(|(a, _)| context_keys.contains(a))
.collect(),
})
}
sender
.send(js_runtime.v8_isolate().thread_safe_handle())
.map_err(|_| {
Error::ExecutionErr("impossible to send v8 isolate".to_string())
})?;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
// pretty frail but this it to make the expr more user friendly and not require the user to write await
let expr = ["variable", "resource"]
.into_iter()
.fold(expr, replace_with_await);
let expr = replace_with_await_result(expr);
let r = runtime.block_on(eval(
&mut js_runtime,
&expr,
context_keys,
by_id,
has_client,
ctx,
))?;
Ok(r) as anyhow::Result<Box<RawValue>>
}),
)
.await
.map_err(|_| {
if let Ok(isolate) = receiver.try_recv() {
isolate.terminate_execution();
let ext = Extension { name: "js_eval", ops: ops.into(), ..Default::default() };
let exts = vec![ext];
// Use our snapshot to provision our new runtime
let options = RuntimeOptions {
extensions: exts,
// startup_snapshot: Some(Snapshot::Static(buffer)),
..Default::default()
};
Error::ExecutionErr(format!(
"The expression of evaluation `{expr2}` took too long to execute (>10000ms)"
))
})??;
}
let mut context_keys = transform_context
.keys()
.filter(|x| expr.contains(&x.to_string()))
.map(|x| x.clone())
.collect_vec();
if !context_keys.contains(&"previous_result".to_string())
&& (p_ids.is_some() && p_ids.as_ref().unwrap().iter().any(|x| expr.contains(x)))
|| expr.contains("error")
{
// tracing::error!("PREVIOUS_RESULT");
context_keys.push("previous_result".to_string());
}
let has_flow_input = expr.contains("flow_input");
if has_flow_input {
context_keys.push("flow_input".to_string())
}
let mut js_runtime = JsRuntime::new(options);
{
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
let mut client = authed_client.clone();
if let Some(client) = client.as_mut() {
client.force_client = Some(
reqwest::ClientBuilder::new()
.user_agent("windmill/beta")
.danger_accept_invalid_certs(
std::env::var("ACCEPT_INVALID_CERTS").is_ok(),
)
.build()
.unwrap(),
);
}
op_state.put(OptAuthedClient(client));
op_state.put(TransformContext {
flow_input: if has_flow_input { flow_input } else { None },
envs: transform_context
.into_iter()
.filter(|(a, _)| context_keys.contains(a))
.collect(),
})
}
sender
.send(js_runtime.v8_isolate().thread_safe_handle())
.map_err(|_| Error::ExecutionErr("impossible to send v8 isolate".to_string()))?;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
// pretty frail but this it to make the expr more user friendly and not require the user to write await
let expr = ["variable", "resource"]
.into_iter()
.fold(expr, replace_with_await);
let expr = replace_with_await_result(expr);
let r = runtime.block_on(eval(
&mut js_runtime,
&expr,
context_keys,
by_id,
has_client,
ctx,
))?;
Ok(r) as anyhow::Result<Box<RawValue>>
}),
)
.await
.map_err(|_| {
if let Ok(isolate) = receiver.try_recv() {
isolate.terminate_execution();
};
Error::ExecutionErr(format!(
"The expression of evaluation `{expr2}` took too long to execute (>10000ms)"
))
})??
}
#[cfg(feature = "deno_core")]
fn replace_with_await(expr: String, fn_name: &str) -> String {
let sep = format!("{}(", fn_name);
let mut split = expr.split(&sep);
@@ -379,12 +345,10 @@ lazy_static! {
Regex::new(r"^(https?)://(([^:@\s]+):([^:@\s]+)@)?([^:@\s]+)(:(\d+))?$").unwrap();
}
#[cfg(feature = "deno_core")]
fn replace_with_await_result(expr: String) -> String {
RE.replace_all(&expr, "(await $r)").to_string()
}
#[cfg(feature = "deno_core")]
fn add_closing_bracket(s: &str) -> String {
let mut s = s.to_string();
let mut level = 1;
@@ -404,7 +368,6 @@ fn add_closing_bracket(s: &str) -> String {
s
}
#[cfg(feature = "deno_core")]
async fn eval(
context: &mut JsRuntime,
expr: &str,
@@ -545,7 +508,6 @@ function get_from_env(name) {{
// }
// TODO: Can we a) share the api configuration here somehow or b) just implement this natively in deno, via the deno client?
#[cfg(feature = "deno_core")]
#[op2(async)]
#[string]
async fn op_variable(
@@ -560,7 +522,6 @@ async fn op_variable(
}
}
#[cfg(feature = "deno_core")]
#[op2(async)]
#[string]
async fn op_get_result(
@@ -579,7 +540,6 @@ async fn op_get_result(
}
}
#[cfg(feature = "deno_core")]
#[op2(async)]
#[string]
async fn op_get_id(
@@ -603,7 +563,6 @@ async fn op_get_id(
}
}
#[cfg(feature = "deno_core")]
#[op2(async)]
#[string]
async fn op_resource(
@@ -621,13 +580,11 @@ async fn op_resource(
}
}
#[cfg(feature = "deno_core")]
pub struct TransformContext {
pub envs: HashMap<String, Arc<Box<RawValue>>>,
pub flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
}
#[cfg(feature = "deno_core")]
#[op2]
#[string]
fn op_get_context(op_state: Rc<RefCell<OpState>>, #[string] id: &str) -> String {
@@ -648,7 +605,6 @@ fn op_get_context(op_state: Rc<RefCell<OpState>>, #[string] id: &str) -> String
}
}
#[cfg(feature = "deno_core")]
pub fn transpile_ts(expr: String) -> anyhow::Result<String> {
let parsed = deno_ast::parse_module(ParseParams {
specifier: url::Url::parse("file:///eval.ts")?,
@@ -665,30 +621,21 @@ pub fn transpile_ts(expr: String) -> anyhow::Result<String> {
.text)
}
#[cfg(not(feature = "deno_core"))]
pub fn transpile_ts(_expr: String) -> anyhow::Result<String> {
Ok("require deno".to_string())
}
#[cfg(feature = "deno_core")]
static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/FETCH_SNAPSHOT.bin"));
#[cfg(feature = "deno_core")]
pub struct MainArgs {
args: Vec<Option<Box<RawValue>>>,
}
#[cfg(feature = "deno_core")]
pub struct LogString {
pub s: String,
}
#[cfg(feature = "deno_core")]
pub struct NativeAnnotation {
pub useragent: Option<String>,
pub proxy: Option<(String, Option<(String, String)>)>,
}
#[cfg(feature = "deno_core")]
pub fn get_annotation(inner_content: &str) -> NativeAnnotation {
let mut res = NativeAnnotation { useragent: None, proxy: None };
@@ -708,7 +655,6 @@ pub fn get_annotation(inner_content: &str) -> NativeAnnotation {
res
}
#[cfg(feature = "deno_core")]
fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> {
RE_PROXY.captures(s).map(|x| {
(
@@ -729,27 +675,7 @@ fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> {
)
})
}
#[cfg(not(feature = "deno_core"))]
pub async fn eval_fetch_timeout(
_env_code: String,
_ts_expr: String,
_js_expr: String,
_args: Option<&Json<HashMap<String, Box<RawValue>>>>,
_job_id: Uuid,
_job_timeout: Option<i32>,
_db: &DB,
_mem_peak: &mut i32,
_canceled_by: &mut Option<CanceledBy>,
_worker_name: &str,
_w_id: &str,
_load_client: bool,
_occupation_metrics: &mut OccupancyMetrics,
) -> anyhow::Result<(Box<RawValue>, String)> {
use serde_json::value::to_raw_value;
Ok((to_raw_value("require deno_core").unwrap(), "".to_string()))
}
#[cfg(feature = "deno_core")]
pub async fn eval_fetch_timeout(
env_code: String,
ts_expr: String,
@@ -925,10 +851,8 @@ pub async fn eval_fetch_timeout(
Ok((res, format!("{extra_logs}{logs}")))
}
#[cfg(feature = "deno_core")]
const WINDMILL_CLIENT: &str = include_str!("./windmill-client.js");
#[cfg(feature = "deno_core")]
async fn eval_fetch(
js_runtime: &mut JsRuntime,
expr: &str,
@@ -974,7 +898,6 @@ import("file:///eval.ts").then((module) => module.main(...args)).then(JSON.strin
Ok(unsafe_raw(r.unwrap_or_else(|| "null".to_string())))
}
#[cfg(feature = "deno_core")]
#[op2]
#[serde]
fn op_get_static_args(op_state: Rc<RefCell<OpState>>) -> Vec<Option<String>> {
@@ -987,7 +910,6 @@ fn op_get_static_args(op_state: Rc<RefCell<OpState>>) -> Vec<Option<String>> {
.collect_vec()
}
#[cfg(feature = "deno_core")]
#[op2(fast)]
fn op_log(op_state: Rc<RefCell<OpState>>, #[string] log: &str) {
// tracing::error!("log: |{}|", log);
@@ -998,7 +920,6 @@ fn op_log(op_state: Rc<RefCell<OpState>>, #[string] log: &str) {
.push_str(log);
}
#[cfg(feature = "deno_core")]
#[cfg(test)]
mod tests {

View File

@@ -34,7 +34,5 @@ pub use worker::*;
pub use result_processor::handle_job_error;
pub use bun_executor::{
get_common_bun_proc_envs, install_bun_lockfile, prebundle_bun_script, prepare_job_dir,
};
pub use bun_executor::{get_common_bun_proc_envs, install_bun_lockfile, prepare_job_dir};
pub use deno_executor::generate_deno_lock;

View File

@@ -29,9 +29,6 @@ lazy_static::lazy_static! {
static ref PYTHON_PATH: String =
std::env::var("PYTHON_PATH").unwrap_or_else(|_| "/usr/local/bin/python3".to_string());
static ref UV_PATH: String =
std::env::var("UV_PATH").unwrap_or_else(|_| "/usr/local/bin/uv".to_string());
static ref FLOCK_PATH: String =
std::env::var("FLOCK_PATH").unwrap_or_else(|_| "/usr/bin/flock".to_string());
static ref NON_ALPHANUM_CHAR: Regex = regex::Regex::new(r"[^0-9A-Za-z=.-]").unwrap();
@@ -39,9 +36,6 @@ lazy_static::lazy_static! {
static ref PIP_TRUSTED_HOST: Option<String> = std::env::var("PIP_TRUSTED_HOST").ok();
static ref PIP_INDEX_CERT: Option<String> = std::env::var("PIP_INDEX_CERT").ok();
static ref USE_PIP_COMPILE: bool = std::env::var("USE_PIP_COMPILE")
.ok().map(|flag| flag == "true").unwrap_or(false);
static ref RELATIVE_IMPORT_REGEX: Regex = Regex::new(r#"(import|from)\s(((u|f)\.)|\.)"#).unwrap();
@@ -67,12 +61,9 @@ use crate::{
handle_child::handle_child,
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, HTTPS_PROXY, HTTP_PROXY,
LOCK_CACHE_DIR, NO_PROXY, NSJAIL_PATH, PATH_ENV, PIP_CACHE_DIR, PIP_EXTRA_INDEX_URL,
PIP_INDEX_URL, TZ_ENV, UV_CACHE_DIR,
PIP_INDEX_URL, TZ_ENV,
};
#[cfg(windows)]
use crate::SYSTEM_ROOT;
pub async fn create_dependencies_dir(job_dir: &str) {
DirBuilder::new()
.recursive(true)
@@ -102,7 +93,7 @@ pub fn handle_ephemeral_token(x: String) -> String {
x
}
pub async fn uv_pip_compile(
pub async fn pip_compile(
job_id: &Uuid,
requirements: &str,
mem_peak: &mut i32,
@@ -112,10 +103,6 @@ pub async fn uv_pip_compile(
worker_name: &str,
w_id: &str,
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
// Fallback to pip-compile. Will be removed in future
mut no_uv: bool,
// Debug-only flag
no_cache: bool,
) -> error::Result<String> {
let mut logs = String::new();
logs.push_str(&format!("\nresolving dependencies..."));
@@ -152,184 +139,83 @@ pub async fn uv_pip_compile(
#[cfg(feature = "enterprise")]
let requirements = replace_pip_secret(db, w_id, &requirements, worker_name, job_id).await?;
let mut req_hash = format!("py-{}", calculate_hash(&requirements));
if no_uv || *USE_PIP_COMPILE {
logs.push_str(&format!("\nFallback to pip-compile (Deprecated!)"));
// Set no_uv if not setted
no_uv = true;
// Make sure that if we put #no_uv (switch to pip-compile) to python code or used `USE_PIP_COMPILE=true` variable.
// Windmill will recalculate lockfile using pip-compile and dont take potentially broken lockfile (generated by uv) from cache (our db).
// It will recalculate lockfile even if inputs have not been changed.
req_hash.push_str("-no_uv");
// Will be in format:
// py-000..000-no_uv
}
if !no_cache {
if let Some(cached) = sqlx::query_scalar!(
"SELECT lockfile FROM pip_resolution_cache WHERE hash = $1",
req_hash
)
.fetch_optional(db)
.await?
{
logs.push_str(&format!("\nfound cached resolution: {req_hash}"));
return Ok(cached);
}
let req_hash = format!("py-{}", calculate_hash(&requirements));
if let Some(cached) = sqlx::query_scalar!(
"SELECT lockfile FROM pip_resolution_cache WHERE hash = $1",
req_hash
)
.fetch_optional(db)
.await?
{
logs.push_str(&format!("\nfound cached resolution: {req_hash}"));
return Ok(cached);
}
let file = "requirements.in";
write_file(job_dir, file, &requirements)?;
// Fallback pip-compile. Will be removed in future
if no_uv {
tracing::debug!("Fallback to pip-compile");
let mut args = vec![
"-q",
"--no-header",
file,
"--resolver=backtracking",
"--strip-extras",
];
let mut pip_args = vec![];
let pip_extra_index_url = PIP_EXTRA_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_extra_index_url.as_ref() {
args.extend(["--extra-index-url", url, "--no-emit-index-url"]);
pip_args.push(format!("--extra-index-url {}", url));
}
let pip_index_url = PIP_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_index_url.as_ref() {
args.extend(["--index-url", url, "--no-emit-index-url"]);
pip_args.push(format!("--index-url {}", url));
}
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
args.extend(["--trusted-host", host]);
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
args.extend(["--cert", cert_path]);
}
let pip_args_str = pip_args.join(" ");
if pip_args.len() > 0 {
args.extend(["--pip-args", &pip_args_str]);
}
tracing::debug!("pip-compile args: {:?}", args);
let mut child_cmd = Command::new("pip-compile");
child_cmd
.current_dir(job_dir)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child_process = start_child_process(child_cmd, "pip-compile").await?;
append_logs(&job_id, &w_id, logs, db).await;
handle_child(
job_id,
db,
mem_peak,
canceled_by,
child_process,
false,
worker_name,
&w_id,
"pip-compile",
None,
false,
occupancy_metrics,
)
let mut args = vec![
"-q",
"--no-header",
file,
"--resolver=backtracking",
"--strip-extras",
];
let mut pip_args = vec![];
let pip_extra_index_url = PIP_EXTRA_INDEX_URL
.read()
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
} else {
let mut args = vec![
"pip",
"compile",
"-q",
"--no-header",
file,
"--strip-extras",
"-o",
"requirements.txt",
// Prefer main index over extra
// https://docs.astral.sh/uv/pip/compatibility/#packages-that-exist-on-multiple-indexes
// TODO: Use env variable that can be toggled from UI
"--index-strategy",
"unsafe-best-match",
// Target to /tmp/windmill/cache/uv
"--cache-dir",
UV_CACHE_DIR,
// We dont want UV to manage python installations
"--python-preference",
"only-system",
"--no-python-downloads",
];
if no_cache {
args.extend(["--no-cache"]);
}
let pip_extra_index_url = PIP_EXTRA_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_extra_index_url.as_ref() {
args.extend(["--extra-index-url", url]);
}
let pip_index_url = PIP_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_index_url.as_ref() {
args.extend(["--index-url", url]);
}
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
args.extend(["--trusted-host", host]);
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
args.extend(["--cert", cert_path]);
}
tracing::debug!("uv args: {:?}", args);
#[cfg(windows)]
let uv_cmd = "uv";
#[cfg(unix)]
let uv_cmd = UV_PATH.as_str();
let mut child_cmd = Command::new(uv_cmd);
child_cmd
.current_dir(job_dir)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child_process = start_child_process(child_cmd, "/usr/local/bin/uv").await?;
append_logs(&job_id, &w_id, logs, db).await;
handle_child(
job_id,
db,
mem_peak,
canceled_by,
child_process,
false,
worker_name,
&w_id,
// TODO: Rename to uv-pip-compile?
"uv",
None,
false,
occupancy_metrics,
)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_extra_index_url.as_ref() {
args.extend(["--extra-index-url", url, "--no-emit-index-url"]);
pip_args.push(format!("--extra-index-url {}", url));
}
let pip_index_url = PIP_INDEX_URL
.read()
.await
.clone()
.map(handle_ephemeral_token);
if let Some(url) = pip_index_url.as_ref() {
args.extend(["--index-url", url, "--no-emit-index-url"]);
pip_args.push(format!("--index-url {}", url));
}
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
args.extend(["--trusted-host", host]);
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
args.extend(["--cert", cert_path]);
}
let pip_args_str = pip_args.join(" ");
if pip_args.len() > 0 {
args.extend(["--pip-args", &pip_args_str]);
}
tracing::debug!("pip-compile args: {:?}", args);
let mut child_cmd = Command::new("pip-compile");
child_cmd
.current_dir(job_dir)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child_process = start_child_process(child_cmd, "pip-compile").await?;
append_logs(&job_id, &w_id, logs, db).await;
handle_child(
job_id,
db,
mem_peak,
canceled_by,
child_process,
false,
worker_name,
&w_id,
"pip-compile",
None,
false,
occupancy_metrics,
)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
let path_lock = format!("{job_dir}/requirements.txt");
let mut file = File::open(path_lock).await?;
let mut req_content = "".to_string();
@@ -513,9 +399,6 @@ except BaseException as e:
let mut reserved_variables = get_reserved_variables(job, &client.token, db).await?;
let additional_python_paths_folders = additional_python_paths.iter().join(":");
#[cfg(windows)]
let additional_python_paths_folders = additional_python_paths_folders.replace(":", ";");
if !*DISABLE_NSJAIL {
let shared_deps = additional_python_paths
.into_iter()
@@ -592,10 +475,6 @@ mount {{
.args(vec!["-u", "-m", "wrapper"])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
python_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
start_child_process(python_cmd, PYTHON_PATH.as_str()).await?
};
@@ -895,7 +774,6 @@ async fn handle_python_deps(
let requirements = match requirements_o {
Some(r) => r,
None => {
let annotation = windmill_common::worker::get_annotation_python(inner_content);
let mut already_visited = vec![];
let requirements = windmill_parser_py_imports::parse_python_imports(
@@ -910,7 +788,7 @@ async fn handle_python_deps(
if requirements.is_empty() {
"".to_string()
} else {
uv_pip_compile(
pip_compile(
job_id,
&requirements,
mem_peak,
@@ -920,8 +798,6 @@ async fn handle_python_deps(
worker_name,
w_id,
occupancy_metrics,
annotation.no_uv,
annotation.no_cache,
)
.await
.map_err(|e| {
@@ -1140,12 +1016,7 @@ pub async fn handle_python_reqs(
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
} else {
let fssafe_req = NON_ALPHANUM_CHAR.replace_all(&req, "_").to_string();
#[cfg(unix)]
let req = format!("'{}'", req);
#[cfg(windows)]
let req = format!("{}", req);
let mut command_args = vec![
PYTHON_PATH.as_str(),
"-m",
@@ -1201,35 +1072,19 @@ pub async fn handle_python_reqs(
tracing::debug!("pip install command: {:?}", command_args);
#[cfg(unix)]
{
let mut flock_cmd = Command::new(FLOCK_PATH.as_str());
flock_cmd
.env_clear()
.envs(envs)
.args([
"-x",
&format!("{}/pip-{}.lock", LOCK_CACHE_DIR, fssafe_req),
"--command",
&command_args.join(" "),
])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
start_child_process(flock_cmd, FLOCK_PATH.as_str()).await?
}
#[cfg(windows)]
{
let mut pip_cmd = Command::new(PYTHON_PATH.as_str());
pip_cmd
.env_clear()
.envs(envs)
.env("SystemRoot", SYSTEM_ROOT.as_str())
.args(&command_args[1..])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
start_child_process(pip_cmd, PYTHON_PATH.as_str()).await?
}
let mut flock_cmd = Command::new(FLOCK_PATH.as_str());
flock_cmd
.env_clear()
.envs(envs)
.args([
"-x",
&format!("{}/pip-{}.lock", LOCK_CACHE_DIR, fssafe_req),
"--command",
&command_args.join(" "),
])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
start_child_process(flock_cmd, FLOCK_PATH.as_str()).await?
};
let child = handle_child(

View File

@@ -23,28 +23,12 @@ use crate::{
RUST_CACHE_DIR, TZ_ENV,
};
#[cfg(windows)]
use crate::SYSTEM_ROOT;
const NSJAIL_CONFIG_RUN_RUST_CONTENT: &str = include_str!("../nsjail/run.rust.config.proto");
lazy_static::lazy_static! {
static ref HOME_DIR: String = std::env::var("HOME").expect("Could not find the HOME environment variable");
static ref CARGO_HOME: String = std::env::var("CARGO_HOME").unwrap_or_else(|_| { CARGO_HOME_DEFAULT.clone() });
static ref RUSTUP_HOME: String = std::env::var("RUSTUP_HOME").unwrap_or_else(|_| { RUSTUP_HOME_DEFAULT.clone() });
static ref CARGO_PATH: String = format!("{}/bin/cargo", CARGO_HOME.as_str());
}
#[cfg(windows)]
lazy_static::lazy_static! {
static ref CARGO_HOME_DEFAULT: String = format!("{}\\.cargo", *HOME_DIR);
static ref RUSTUP_HOME_DEFAULT: String = format!("{}\\.rustup", *HOME_DIR);
}
#[cfg(unix)]
lazy_static::lazy_static! {
static ref CARGO_HOME_DEFAULT: String = "/usr/local/cargo".to_string();
static ref RUSTUP_HOME_DEFAULT: String = "/usr/local/rustup".to_string();
static ref CARGO_HOME: String = std::env::var("CARGO_HOME").unwrap_or_else(|_| "/usr/local/cargo".to_string());
static ref RUSTUP_HOME: String = std::env::var("RUSTUP_HOME").unwrap_or_else(|_| "/usr/local/rustup".to_string());
static ref CARGO_PATH: String = format!("{}/bin/cargo", std::env::var("CARGO_HOME").unwrap_or("/usr/local/cargo/bin/cargo".to_string()));
}
const RUST_OBJECT_STORE_PREFIX: &str = "rustbin/";
@@ -142,14 +126,6 @@ pub async fn generate_cargo_lockfile(
.args(vec!["generate-lockfile"])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
gen_lockfile_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
gen_lockfile_cmd.env(
"TMP",
std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()),
);
}
let gen_lockfile_process = start_child_process(gen_lockfile_cmd, CARGO_PATH.as_str()).await?;
handle_child(
job_id,
@@ -200,16 +176,6 @@ pub async fn build_rust_crate(
.args(vec!["build", "--release"])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
build_rust_cmd.env("SystemRoot", SYSTEM_ROOT.as_str());
build_rust_cmd.env(
"TMP",
std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()),
);
}
let build_rust_process = start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await?;
handle_child(
job_id,
@@ -313,13 +279,7 @@ pub async fn handle_rust_job(
let cache_logs = if cache {
let target = format!("{job_dir}/main");
#[cfg(unix)]
let symlink = std::os::unix::fs::symlink(&bin_path, &target);
#[cfg(windows)]
let symlink = std::os::windows::fs::symlink_dir(&bin_path, &target);
symlink.map_err(|e| {
std::os::unix::fs::symlink(&bin_path, &target).map_err(|e| {
Error::ExecutionErr(format!(
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
))
@@ -400,9 +360,6 @@ pub async fn handle_rust_job(
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
run_rust.env("SystemRoot", SYSTEM_ROOT.as_str());
start_child_process(run_rust, compiled_executable_name).await?
};
handle_child(

View File

@@ -237,7 +237,6 @@ pub const ROOT_CACHE_NOMOUNT_DIR: &str = concatcp!(TMP_DIR, "/cache_nomount/");
pub const LOCK_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "lock");
pub const PIP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "pip");
pub const UV_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "uv");
pub const TAR_PIP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/pip");
pub const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno");
pub const DENO_CACHE_DIR_DEPS: &str = concatcp!(ROOT_CACHE_DIR, "deno/deps");
@@ -349,6 +348,7 @@ lazy_static::lazy_static! {
pub static ref PIP_INDEX_URL: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
pub static ref JOB_DEFAULT_TIMEOUT: Arc<RwLock<Option<i32>>> = Arc::new(RwLock::new(None));
static ref MAX_TIMEOUT: u64 = std::env::var("TIMEOUT")
.ok()
.and_then(|x| x.parse::<u64>().ok())
@@ -389,12 +389,6 @@ lazy_static::lazy_static! {
}
#[cfg(windows)]
lazy_static::lazy_static! {
pub static ref SYSTEM_ROOT: String = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
}
//only matter if CLOUD_HOSTED
pub const MAX_RESULT_SIZE: usize = 1024 * 1024 * 2; // 2MB

View File

@@ -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::{get_annotation_ts, to_raw_value, to_raw_value_owned, write_file};
use windmill_common::worker::{get_annotation, to_raw_value, to_raw_value_owned, write_file};
use windmill_common::{
error::{self, to_anyhow},
flows::FlowValue,
@@ -26,7 +26,7 @@ use windmill_parser_ts::parse_expr_for_imports;
use windmill_queue::{append_logs, CanceledBy, PushIsolationLevel};
use crate::common::OccupancyMetrics;
use crate::python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile};
use crate::python_executor::{create_dependencies_dir, handle_python_reqs, pip_compile};
use crate::rust_executor::{build_rust_crate, compute_rust_hash, generate_cargo_lockfile};
use crate::{
bun_executor::gen_bun_lockfile,
@@ -953,7 +953,7 @@ async fn lock_modules<'c>(
}
if language == ScriptLang::Bun || language == ScriptLang::Bunnative {
let anns = get_annotation_ts(&content);
let anns = get_annotation(&content);
if anns.native_mode && language == ScriptLang::Bun {
language = ScriptLang::Bunnative;
} else if !anns.native_mode && language == ScriptLang::Bunnative {
@@ -1003,7 +1003,7 @@ async fn lock_modules<'c>(
fn skip_creating_new_lock(language: &ScriptLang, content: &str) -> bool {
if language == &ScriptLang::Bun || language == &ScriptLang::Bunnative {
let anns = get_annotation_ts(&content);
let anns = get_annotation(&content);
if anns.native_mode && language == &ScriptLang::Bun {
return false;
} else if !anns.native_mode && language == &ScriptLang::Bunnative {
@@ -1077,7 +1077,7 @@ 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_ts(&content);
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
@@ -1280,7 +1280,7 @@ async fn python_dep(
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
) -> std::result::Result<String, Error> {
create_dependencies_dir(job_dir).await;
let req: std::result::Result<String, Error> = uv_pip_compile(
let req: std::result::Result<String, Error> = pip_compile(
job_id,
&reqs,
mem_peak,
@@ -1290,8 +1290,6 @@ async fn python_dep(
worker_name,
w_id,
occupancy_metrics,
false,
false,
)
.await;
// install the dependencies to pre-fill the cache
@@ -1436,9 +1434,8 @@ async fn capture_dependency_job(
.await
}
ScriptLang::Bun | ScriptLang::Bunnative => {
let npm_mode = npm_mode.unwrap_or_else(|| {
windmill_common::worker::get_annotation_ts(job_raw_code).npm_mode
});
let npm_mode = npm_mode
.unwrap_or_else(|| windmill_common::worker::get_annotation(job_raw_code).npm_mode);
if !raw_deps {
let _ = write_file(job_dir, "main.ts", job_raw_code)?;
}
@@ -1475,7 +1472,7 @@ async fn capture_dependency_job(
base_internal_url,
worker_name,
&token,
&mut Some(occupancy_metrics),
occupancy_metrics,
)
.await?;
}

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.405.3";
export const VERSION = "v1.403.1";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -35,7 +35,7 @@ import {
pushInstanceConfigs,
type SimplifiedSettings,
} from "./settings.ts";
import { sleep, deepEqual } from "./utils.ts";
import { deepEqual } from "./utils.ts";
import { GlobalOptions } from "./types.ts";
export interface Instance {
@@ -190,12 +190,6 @@ export async function pickInstance(
const instances = await allInstances();
if (opts.baseUrl && opts.token) {
log.info("Using instance fully defined by --base-url and --token");
setClient(
opts.token,
opts.baseUrl.endsWith("/") ? opts.baseUrl.slice(0, -1) : opts.baseUrl
);
return {
name: "custom",
remote: opts.baseUrl,
@@ -293,8 +287,6 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
log.info("No instance-level changes to apply");
}
sleep(1000);
if (opts.includeWorkspaces) {
log.info("\nPulling all workspaces");
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
@@ -308,7 +300,6 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
const rootDir = Deno.cwd();
for (const remoteWorkspace of remoteWorkspaces) {
log.info("\nPulling workspace " + remoteWorkspace.id);
sleep(1000);
const workspaceName = instance.prefix + "_" + remoteWorkspace.id;
await Deno.mkdir(path.join(rootDir, workspaceName), {
recursive: true,
@@ -336,7 +327,6 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
includeSettings: true,
includeUsers: true,
includeKey: true,
yes: opts.yes,
});
}
@@ -345,14 +335,12 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
);
if (localWorkspacesToDelete.length > 0) {
const confirmDelete =
opts.yes ||
(await Confirm.prompt({
message:
"Do you want to delete the local copy of workspaces that don't exist anymore on the instance?\n" +
localWorkspacesToDelete.map((w) => w.workspaceId).join(", "),
default: true,
}));
const confirmDelete = await Confirm.prompt({
message:
"Do you want to delete the local copy of workspaces that don't exist anymore on the instance?\n" +
localWorkspacesToDelete.map((w) => w.workspaceId).join(", "),
default: true,
});
if (confirmDelete) {
for (const workspace of localWorkspacesToDelete) {
@@ -421,8 +409,6 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
log.info("No instance-level changes to apply");
}
sleep(1000);
if (opts.includeWorkspaces) {
instances = await allInstances();
const localPrefix = (await Select.prompt({
@@ -436,20 +422,20 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
default: instance.prefix as unknown,
})) as unknown as string;
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
page: 1,
perPage: 1000,
});
let localWorkspaces = await allWorkspaces();
const rootDir = Deno.cwd();
let localWorkspaces = Deno.readDir(".");
localWorkspaces = localWorkspaces.filter((w) =>
w.name.startsWith(localPrefix + "_")
);
log.info("\nPushing all workspaces");
const rootDir = Deno.cwd();
log.info(
`\nPushing all workspaces: ${localWorkspaces.join(
", "
)} with prefix ${localPrefix}`
);
for (const localWorkspace of localWorkspaces) {
log.info("\nPushing workspace " + localWorkspace.workspaceId);
sleep(1000);
try {
await Deno.chdir(path.join(rootDir, localWorkspace.name));
} catch (_) {
@@ -490,22 +476,23 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
includeSettings: true,
includeUsers: true,
includeKey: true,
yes: opts.yes,
});
}
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
page: 1,
perPage: 1000,
});
const workspacesToDelete = remoteWorkspaces.filter(
(w) => !localWorkspaces.find((l) => l.workspaceId === w.id)
);
if (workspacesToDelete.length > 0) {
const confirmDelete =
opts.yes ||
(await Confirm.prompt({
message:
"Do you want to delete the following remote workspaces that don't exist locally?\n" +
workspacesToDelete.map((w) => w.id).join(", "),
default: true,
}));
const confirmDelete = await Confirm.prompt({
message:
"Do you want to delete the following remote workspaces that don't exist locally?\n" +
workspacesToDelete.map((w) => w.id).join(", "),
default: true,
});
if (confirmDelete) {
for (const workspace of workspacesToDelete) {

View File

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

View File

@@ -504,7 +504,7 @@ function ZipFSElement(
if (formatExtension) {
const fileContent: string = parsed["value"]["content"];
if (typeof fileContent === "string") {
if (typeof(fileContent) === "string") {
r.push({
isDirectory: false,
path:
@@ -948,7 +948,7 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
if (
!opts.yes &&
!(await Confirm.prompt({
message: `Do you want to apply these ${changes.length} changes to your local files?`,
message: `Do you want to apply these ${changes.length} changes?`,
default: true,
}))
) {
@@ -1251,7 +1251,7 @@ export async function push(opts: GlobalOptions & SyncOptions) {
if (
!opts.yes &&
!(await Confirm.prompt({
message: `Do you want to apply these ${changes.length} changes to the remote?`,
message: `Do you want to apply these ${changes.length} changes?`,
default: true,
}))
) {

View File

@@ -17,9 +17,6 @@ ENV TZ=Etc/UTC
RUN /usr/local/bin/python3 -m pip install pip-tools
# Install UV
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv
COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun
# add the docker client to call docker from a worker if enabled

View File

@@ -16,8 +16,6 @@ RUN apt-get -y update && apt-get install -y curl nodejs awscli
ENV TZ=Etc/UTC
RUN /usr/local/bin/python3 -m pip install pip-tools
# Install UV
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.4.18/uv-installer.sh | sh && mv /root/.cargo/bin/uv /usr/local/bin/uv
COPY --from=oven/bun:1.1.27 /usr/local/bin/bun /usr/bin/bun

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.405.3",
"version": "1.403.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -177,11 +177,6 @@
"svelte": "./package/components/icons/WindmillIcon.svelte",
"default": "./package/components/icons/WindmillIcon.svelte"
},
"./components/icons/WindmillIcon2.svelte": {
"types": "./package/components/icons/WindmillIcon2.d.ts",
"svelte": "./package/components/icons/WindmillIcon2.svelte",
"default": "./package/components/icons/WindmillIcon2.svelte"
},
"./components/IconedResourceType.svelte": {
"types": "./package/components/IconedResourceType.svelte.d.ts",
"svelte": "./package/components/IconedResourceType.svelte",
@@ -363,9 +358,6 @@
"components/icons/WindmillIcon.svelte": [
"./package/components/icons/WindmillIcon.svelte.d.ts"
],
"components/icons/WindmillIcon2.svelte": [
"./package/components/icons/WindmillIcon2.svelte.d.ts"
],
"components/scriptEditor/LogPanel.svelte": [
"./package/components/scriptEditor/LogPanel.svelte.d.ts"
],

View File

@@ -7,14 +7,10 @@
import DefaultScriptsInner from './DefaultScriptsInner.svelte'
let drawer: Drawer
export let placement: 'left' | 'right' = 'left'
export let size: 'xs3' | 'xs2' = 'xs2'
export let noText = false
</script>
{#if $userStore?.is_admin || $userStore?.is_super_admin}
<Drawer bind:this={drawer} {placement}>
<Drawer bind:this={drawer} placement="left">
<DrawerContent title="Edit Default Scripts" on:close={drawer.closeDrawer}>
<DefaultScriptsInner />
</DrawerContent>
@@ -23,10 +19,8 @@
on:click={drawer?.openDrawer}
startIcon={{ icon: SettingsIcon }}
color="light"
{size}
size="xs2"
btnClasses="!text-tertiary"
variant="contained"
variant="contained">defaults</Button
>
{noText ? '' : 'defaults'}
</Button>
{/if}

View File

@@ -6,7 +6,6 @@
import { defaultScriptLanguages } from '$lib/scripts'
import Alert from './common/alert/Alert.svelte'
export let small = false
$: langs = computeLangs($defaultScripts)
function computeLangs(defaultScripts: WorkspaceDefaultScripts | undefined): Script['language'][] {
@@ -32,32 +31,24 @@
}
</script>
<Alert title="Global to workspace" type="info" class="mb-4" size={small ? 'xs' : 'sm'}>
<Alert title="Global to workspace" type="info" class="mb-4">
This setting is only available to admins and will affect all users in the workspace.
</Alert>
<div class="h-full w-full flex-col {small ? 'gap-0' : 'gap-2'} flex">
<div class="h-full w-full flex-col gap-2 flex">
{#each langs as lang, i (lang)}
<div
animate:flip={{ duration: 300 }}
class="w-full p-2 rounded {small
? ''
: 'border border-secondary'} grid grid-cols-3 items-center"
><h3 class={small ? 'text-xs font-medium justify-center' : ''}>{lang}</h3>
class="w-full p-2 rounded border border-seconadry grid grid-cols-3"
><h3>{lang}</h3>
<div>
{#if i > 0}
<button
on:click={() => changePosition(i ?? 0, true)}
class={small ? 'mr-2 text-secondary text-sm' : 'text-lg mr-2'}
title="Move up"
>
<button on:click={() => changePosition(i ?? 0, true)} class="text-lg mr-2">
&uparrow;</button
>
{/if}
{#if i < langs.length - 1}
<button
on:click={() => changePosition(i ?? 0, false)}
class={small ? 'mr-2 text-secondary text-sm' : 'text-lg mr-2'}
title="Move down">&downarrow;</button
<button on:click={() => changePosition(i ?? 0, false)} class="text-lg mr-2"
>&downarrow;</button
>
{/if}</div
>

View File

@@ -496,8 +496,7 @@
saveDraft: () => {},
initialPath: '',
flowInputsStore: writable<FlowInput>({}),
customUi: {},
insertButtonOpen: writable(false)
customUi: {}
})
$: updateFlow($flowStore)

View File

@@ -394,7 +394,6 @@
selectedIdStore.set(selectedId)
}
let insertButtonOpen = writable<boolean>(false)
setContext<FlowEditorContext>('FlowEditorContext', {
selectedId: selectedIdStore,
schedule: scheduleStore,
@@ -409,8 +408,7 @@
saveDraft,
initialPath,
flowInputsStore: writable<FlowInput>({}),
customUi,
insertButtonOpen
customUi
})
async function loadSchedule() {
@@ -463,24 +461,20 @@
}
break
case 'ArrowDown': {
if (!$insertButtonOpen) {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > -1 && idx < ids.length - 1) {
$selectedIdStore = ids[idx + 1]
event.preventDefault()
}
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > -1 && idx < ids.length - 1) {
$selectedIdStore = ids[idx + 1]
event.preventDefault()
}
break
}
case 'ArrowUp': {
if (!$insertButtonOpen) {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > 0 && idx < ids.length) {
$selectedIdStore = ids[idx - 1]
event.preventDefault()
}
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > 0 && idx < ids.length) {
$selectedIdStore = ids[idx - 1]
event.preventDefault()
}
break
}
@@ -567,8 +561,6 @@
kind: string
app: string
ask_id: number
id: number
version_id: number
}[]
} catch (err) {
if (err.name !== 'CancelError') throw err
@@ -898,8 +890,7 @@
const snakeKey = snakeCase(key)
if (
schemaProperty &&
(!$flowStore.schema ||
!(snakeKey in ($flowStore?.schema?.properties ?? ({} as any)))) // prevent overriding flow inputs
(!$flowStore.schema || !(snakeKey in ($flowStore.schema.properties as any) ?? {})) // prevent overriding flow inputs
) {
copilotFlowInputs[snakeKey] = schemaProperty
if (schema?.required.includes(snakeKey)) {

View File

@@ -22,7 +22,6 @@
export let workspaceId: string | undefined = undefined
export let refreshLog: boolean = false
export let durationStates: Writable<Record<string, DurationStatus>> | undefined
export let downloadLogs = true
let lastJobId: string | undefined = undefined
let drawer: Drawer | undefined = undefined
@@ -41,6 +40,7 @@
let logOffset = 0
async function getLogs() {
if (jobId) {
const getUpdate = await JobService.getJobUpdates({
workspace: workspaceId ?? $workspaceStore!,
id: jobId,
@@ -85,12 +85,6 @@
<div class="absolute z-40 text-xs top-0 left-1"
><button class="" on:click={drawer.openDrawer}>explore all steps' logs</button></div
>
<LogViewer
download={downloadLogs}
content={logs ?? ''}
{jobId}
isLoading={waitingForExecutor}
{tag}
/>
<LogViewer content={logs ?? ''} {jobId} isLoading={waitingForExecutor} {tag} />
</div>
</div>

View File

@@ -18,7 +18,6 @@
export let hideDownloadInGraph = false
export let hideNodeDefinition = false
export let hideJobId = false
export let hideDownloadLogs = false
export let isOwner = false
export let wideResults = false
@@ -34,8 +33,7 @@
hideDownloadInGraph,
hideNodeDefinition,
hideTimeline,
hideJobId,
hideDownloadLogs
hideJobId
})
function loadOwner(path: string) {

View File

@@ -38,8 +38,7 @@
suspendStatus,
hideDownloadInGraph,
hideTimeline,
hideNodeDefinition,
hideDownloadLogs
hideNodeDefinition
} = getContext<FlowStatusViewerContext>('FlowStatusViewer')
export let jobId: string
@@ -670,7 +669,6 @@
result={job.result}
logs={job.logs}
durationStates={localDurationStatuses}
downloadLogs={!hideDownloadLogs}
/>
</div>
{/if}
@@ -1028,7 +1026,6 @@
result={job['result']}
logs={job.logs ?? ''}
durationStates={localDurationStatuses}
downloadLogs={!hideDownloadLogs}
/>
{:else if selectedNode == 'start'}
{#if job.args}
@@ -1104,7 +1101,6 @@
tag={node.tag}
logs={node.logs}
durationStates={localDurationStatuses}
downloadLogs={!hideDownloadLogs}
/>
{:else}
<p class="p-2 text-tertiary italic"

View File

@@ -47,6 +47,7 @@
<div class="flex w-full">
<input
bind:this={inputDiv}
autofocus
type="text"
bind:value
class="!w-auto grow"

View File

@@ -28,7 +28,6 @@
export let drawerOpen = false
export let noMaxH = false
export let noAutoScroll = false
export let download = true
// @ts-ignore
const ansi_up = new AnsiUp()
@@ -94,7 +93,7 @@
loadedFromObjectStore: string,
limit: number
) {
let content = loadedFromObjectStore + (jobContent ?? '')
let content = loadedFromObjectStore + jobContent ?? ''
if (content.length > limit) {
return content.substring(content.length - limit)
}
@@ -149,7 +148,7 @@
<Drawer bind:this={logViewer} bind:open={drawerOpen} size="900px">
<DrawerContent title="Expanded Logs" on:close={logViewer.closeDrawer}>
<svelte:fragment slot="actions">
{#if jobId && download}
{#if jobId}
<Button
href="{base}/api/w/{$workspaceStore}/jobs_u/get_logs/{jobId}"
download="windmill_logs_{jobId}.txt"
@@ -197,7 +196,7 @@
>
<div class="sticky z-10 top-0 right-0 w-full flex flex-row-reverse justify-between text-sm">
<div class="flex gap-2 pl-0.5 bg-surface-secondary">
{#if jobId && download}
{#if jobId}
<div class="flex items-center">
<a
class="text-primary pb-0.5"

View File

@@ -1,57 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte'
import { twMerge } from 'tailwind-merge'
let isAtBottom: boolean = false
let isScrollable = false
export let scrollableClass: string = ''
export let shiftedShadow: boolean = false
let mutationObserver: MutationObserver
let el: HTMLDivElement
function handleScroll(event) {
const scrollableElement = event.target
isAtBottom =
scrollableElement.scrollTop + scrollableElement.offsetHeight >=
scrollableElement.scrollHeight - 2
}
function checkIfScrollable(el) {
return el.scrollHeight > el.clientHeight
}
function observeScrollability(el) {
isScrollable = checkIfScrollable(el)
mutationObserver = new MutationObserver(() => {
isScrollable = checkIfScrollable(el)
})
mutationObserver?.observe(el, { childList: true, subtree: true, characterData: true })
}
export function scrollIntoView(top: number) {
el.scrollTo({ top, behavior: 'smooth' })
}
onMount(() => {
observeScrollability(el)
})
onDestroy(() => {
mutationObserver?.disconnect()
})
</script>
<div class={twMerge('relative pb-1', scrollableClass)}>
<div bind:this={el} on:scroll={handleScroll} class="w-full h-full overflow-y-auto">
<slot />
</div>
{#if !isAtBottom && isScrollable}
<div
class="pointer-events-none absolute bottom-0 {shiftedShadow
? 'left-2'
: 'right-0'} h-14 w-full bg-gradient-to-t from-surface to-transparent"
/>
{/if}
</div>

View File

@@ -1,15 +0,0 @@
<script lang="ts">
import { WindmillIcon2 } from './icons'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import { Building } from 'lucide-svelte'
export let selected: 'all' | 'hub' | 'workspace' = 'all'
</script>
<div class="max-w-min">
<ToggleButtonGroup bind:selected>
<ToggleButton value="all" label="All" light small />
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" light small />
<ToggleButton value="workspace" icon={Building} label="Workspace" light small />
</ToggleButtonGroup>
</div>

View File

@@ -258,7 +258,7 @@
<IdEditor
{id}
on:selected={() => ($selectedComponent = [id])}
on:save={({ detail }) => renameId(detail)}
on:change={({ detail }) => renameId(detail)}
/></div
>
{/if}

View File

@@ -54,8 +54,7 @@
'bottom-start': 'origin-top-left left-0',
'bottom-end': 'origin-top-right right-0',
'top-start': 'origin-bottom-left left-0 bottom-0',
'top-end': 'origin-bottom-right right-0 bottom-0',
'top-center': 'origin-top-left -top-full left-1/2 transform -translate-x-1/2 -translate-y-full'
'top-end': 'origin-bottom-right right-0 bottom-0'
}
const dispatch = createEventDispatcher()
</script>

View File

@@ -9,13 +9,12 @@
}
export let containerClasses: string = 'rounded-lg shadow-md border p-4 bg-surface'
export let floatingClasses: string = ''
const [floatingRef, floatingContent] = createFloatingActions(floatingConfig)
export let blockOpen = false
export let shouldUsePortal: boolean = true
export let target: string | HTMLElement | undefined = undefined
export let noTransition = false
</script>
<Popover on:close class="leading-none">
@@ -25,30 +24,22 @@
</div>
</PopoverButton>
<ConditionalPortal condition={shouldUsePortal} {target}>
<div use:floatingContent class={`z5000 ${floatingClasses}`}>
{#if !noTransition}
<Transition
show={blockOpen || undefined}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<PopoverPanel let:close static={blockOpen}>
<div class={containerClasses}>
<slot {close} />
</div>
</PopoverPanel>
</Transition>
{:else}
<PopoverPanel focus={false} let:close static={blockOpen}>
<div use:floatingContent class="z5000">
<Transition
show={blockOpen || undefined}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<PopoverPanel let:close static={blockOpen}>
<div class={containerClasses}>
<slot {close} />
</div>
</PopoverPanel>
{/if}
</Transition>
</div>
</ConditionalPortal>
</Popover>

View File

@@ -1,63 +0,0 @@
<script lang="ts">
import Portal from '$lib/components/Portal.svelte'
import { clickOutside } from '$lib/utils'
import { createFloatingActions, type ComputeConfig } from 'svelte-floating-ui'
export let floatingConfig: ComputeConfig = {
strategy: 'absolute',
//@ts-ignore
placement: 'bottom-center'
}
export let open = false
export let target: string | undefined = undefined
// export let containerClasses: string = 'rounded-lg shadow-md border p-4 bg-surface'
// export let floatingClasses: string = ''
const [floatingRef, floatingContent] = createFloatingActions(floatingConfig)
function close(div: Element | null) {
open = false
}
let acceptClickoutside = false
function pointerup() {
setTimeout(() => {
acceptClickoutside = true
}, 100)
}
function pointerdown() {
if (acceptClickoutside && open) {
open = false
} else {
acceptClickoutside = false
open = true
}
}
</script>
<div use:floatingRef>
<slot {pointerup} {pointerdown} name="button" />
</div>
<Portal {target}>
{#if open}
<div
class="border rounded-lg shadow-lg bg-surface z5000"
style="position:absolute"
use:floatingContent
>
<div
use:clickOutside
on:click_outside={() => {
if (acceptClickoutside) {
acceptClickoutside = false
open = false
}
}}
>
<slot {close} />
</div>
</div>
{/if}
</Portal>

View File

@@ -19,7 +19,7 @@
const dispatch = createEventDispatcher()
async function onGenerate() {
if (funcDesc?.length <= 0) {
if (funcDesc.length <= 0) {
return
}
savePrompt()
@@ -122,7 +122,7 @@
bind:this={input}
bind:value={funcDesc}
on:keypress={({ key }) => {
if (key === 'Enter' && funcDesc?.length > 0) {
if (key === 'Enter' && funcDesc.length > 0) {
close(input || null)
onGenerate()
}
@@ -139,7 +139,7 @@
close(input || null)
onGenerate()
}}
disabled={funcDesc?.length <= 0}
disabled={funcDesc.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>

View File

@@ -110,7 +110,7 @@
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="text-primary transition-all {funcDesc?.length > 0 ? 'w-96' : 'w-60'}">
<div class="text-primary transition-all {funcDesc.length > 0 ? 'w-96' : 'w-60'}">
<div>
<div class="flex p-2 relative">
<input
@@ -118,7 +118,7 @@
bind:this={input}
bind:value={funcDesc}
on:input={() => {
if (funcDesc?.length > 2) {
if (funcDesc.length > 2) {
getHubCompletions(funcDesc)
} else {
hubCompletions = []
@@ -126,14 +126,14 @@
}}
placeholder="Search {trigger ? 'triggers' : 'scripts'} or AI gen"
/>
{#if funcDesc?.length === 0}
{#if funcDesc.length === 0}
<Wand2
size={14}
class="absolute right-4 top-1/2 -translate-y-1/2 fill-current opacity-70 text-violet-800 dark:text-violet-400"
/>
{/if}
</div>
{#if !disableAi && funcDesc?.length > 0}
{#if !disableAi && funcDesc.length > 0}
<ul class="transition-all divide-y">
<li>
<button
@@ -181,11 +181,11 @@
</li>
</ul>
{/if}
{#if funcDesc?.length > 0 && filteredItems?.length > 0}
{#if funcDesc.length > 0 && filteredItems.length > 0}
<div class="text-left mt-2">
<p class="text-xs text-secondary ml-2">Workspace {trigger ? 'Triggers' : 'Scripts'}</p>
<ul class="transition-all divide-y">
{#each filteredItems?.slice(0, 3) ?? [] as item (item.path)}
{#each filteredItems.slice(0, 3) as item (item.path)}
<li>
<button
class="py-2 gap-4 flex flex-row hover:bg-surface-hover transition-all items-center justify-between w-full"

View File

@@ -1,68 +0,0 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { ScriptService, type Script } from '$lib/gen'
import { Wand2, Loader2 } from 'lucide-svelte'
import SearchItems from '../SearchItems.svelte'
import { emptyString } from '$lib/utils'
import { onMount } from 'svelte'
export let funcDesc: string
export let trigger = false
export let loading = false
export let preFilter: string
export let disableAi = false
let scripts: Script[] | undefined = undefined
export let filteredItems: (Script & { marked?: string })[] | (Item & { marked?: string })[] = []
$: prefilteredItems = scripts ?? []
async function loadScripts(): Promise<void> {
const loadedScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
perPage: 300,
kinds: trigger ? 'trigger' : 'script'
})
scripts = loadedScripts
}
$: scripts == undefined && funcDesc?.length > 1 && loadScripts()
let input: HTMLInputElement
$: preFilter &&
setTimeout(() => {
input?.focus()
}, 50)
onMount(() => {
input?.focus()
})
</script>
<SearchItems
filter={funcDesc}
items={prefilteredItems}
bind:filteredItems
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="relative text-primary items-center transition-all flex-grow">
<div class="grow items-cente">
<input
bind:this={input}
type="text"
bind:value={funcDesc}
placeholder="Search {trigger ? 'triggers' : 'scripts'} {disableAi ? '' : 'or AI gen'}"
/>
</div>
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none">
{#if loading}
<Loader2 size={16} class="animate-spin text-gray-400" />
{/if}
{#if funcDesc?.length === 0 && !loading && !disableAi}
<Wand2 size={14} class="fill-current opacity-70 text-violet-800 dark:text-violet-400" />
{/if}
</div>
</div>

View File

@@ -14,7 +14,7 @@ import { scriptLangToEditorLang } from '$lib/scripts'
export type FlowCopilotModule = {
id: string
type: 'trigger' | 'script'
type: 'trigger' | 'script'
description: string
code: string
source: 'hub' | 'custom' | undefined
@@ -25,8 +25,6 @@ export type FlowCopilotModule = {
kind: string
app: string
ask_id: number
id: number
version_id: number
}[]
selectedCompletion:
| {
@@ -106,69 +104,6 @@ To maintain state across runs, you can use get_state() and set_state(value) whic
{additionalInformation}`
}
// const preprocessorPrompts: {
// bun: string
// python3: string
// } = {
// bun: `I'm building a workflow which is a sequence of script steps. Write the preprocessor step in {codeLang} which should check for {description} and return an array.
// The preprocessor step is executed before flow begins to map trigger specific inputs to the flow inputs.
// Here is an example of what the preprocessor step should look like:
// \`\`\`{codeLang}
// export async function preprocessor(
// wm_trigger: {
// kind: 'http' | 'email' | 'webhook',
// http?: {
// route: string // The route path, e.g. "/users/:id"
// path: string // The actual path called, e.g. "/users/123"
// method: string
// params: Record<string, string>
// query: Record<string, string>
// headers: Record<string, string>
// }
// },
// /* your other args */
// ) {
// return {
// // return the args to be passed to the flow
// }
// }
// \`\`\`
// {additionalInformation}`,
// python3: `I'm building a workflow which is a sequence of script steps. Write the preprocessor step in {codeLang} which should check for {description} and return an array.
// The preprocessor step is executed before flow begins to map trigger specific inputs to the flow inputs.
// Here is an example of what the preprocessor step should look like:
// \`\`\`{codeLang}
// from typing import TypedDict, Literal
// class Http(TypedDict):
// route: str # The route path, e.g. "/users/:id"
// path: str # The actual path called, e.g. "/users/123"
// method: str
// params: dict[str, str]
// query: dict[str, str]
// headers: dict[str, str]
// class WmTrigger(TypedDict):
// kind: Literal["http", "email", "webhook"]
// http: Http | None
// def preprocessor(
// wm_trigger: WmTrigger,
// # your other args
// ):
// return {
// # return the args to be passed to the flow
// }
// \`\`\`
// {additionalInformation}`
// }
const firstActionPrompt = `I'm building a workflow which is a sequence of script steps. Write a script in {codeLang} which should {description}.
Return the script's output.
@@ -265,8 +200,6 @@ export async function stepCopilot(
let prompt =
module.type === 'trigger'
? triggerPrompts[lang]
// : module.type === 'preprocessor'
// ? preprocessorPrompts[lang]
: pastModule === undefined
? firstActionPrompt
: isFirstInLoop

View File

@@ -18,14 +18,13 @@
export let disableSettings = false
export let smallErrorHandler = false
let size = 50
let size = 40
const { currentStepStore: copilotCurrentStepStore } =
getContext<FlowCopilotContext>('FlowCopilotContext')
</script>
<div
id="flow-editor"
class={classNames(
'h-full overflow-hidden transition-colors duration-[400ms] ease-linear border-t',
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : ''

View File

@@ -1,73 +0,0 @@
<script lang="ts">
import { Skeleton } from '$lib/components/common'
import SearchItems from '$lib/components/SearchItems.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString } from '$lib/utils'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import { createEventDispatcher } from 'svelte'
// export let failureModule: boolean
const dispatch = createEventDispatcher()
let items: Flow[] | undefined = undefined
let filteredItems: (Flow & { marked?: string })[] | undefined = undefined
export let filter = ''
$: $workspaceStore && loadFlows()
let ownerFilter: string | undefined = undefined
$: prefilteredItems = ownerFilter ? items?.filter((x) => x.path.startsWith(ownerFilter!)) : items
export let owners: string[] = []
$: owners = Array.from(
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
async function loadFlows() {
items = await FlowService.listFlows({ workspace: $workspaceStore! })
}
</script>
<SearchItems
{filter}
items={prefilteredItems}
bind:filteredItems
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="flex flex-col min-h-0">
{#if filteredItems}
{#if filter.length > 0 && filteredItems.length == 0}
<div class="text-2xs text-tercary font-extralight text-center py-2 px-3 items-center">
No items found.
</div>
{/if}
<ul class="overflow-auto">
{#each filteredItems as { path, summary, marked }}
<li class="flex flex-row w-full">
<button
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover bg-surface transition-all items-center rounded-md text-left text-2xs text-primary font-normal"
on:click={async () => {
dispatch('pickFlow', {
path,
summary
})
}}
>
<BarsStaggered size={14} />
<span class="grow truncate">
{#if marked}
{@html marked}
{:else}
{!summary || summary.length == 0 ? path : summary}
{/if}
</span>
</button>
</li>
{/each}
</ul>
{:else}
{#each Array(10).fill(0) as _}
<Skeleton layout={[0.5, [1.5]]} />
{/each}
{/if}
</div>

View File

@@ -1,466 +0,0 @@
<script lang="ts">
import { isCloudHosted } from '$lib/cloud'
import { sendUserToast } from '$lib/toast'
import FlowScriptPickerQuick from '../pickers/FlowScriptPickerQuick.svelte'
import WorkspaceScriptPickerQuick from '../pickers/WorkspaceScriptPickerQuick.svelte'
import { defaultScriptLanguages, processLangs } from '$lib/scripts'
import { defaultScripts, enterpriseLicense, userStore } from '$lib/stores'
import type { SupportedLanguage } from '$lib/common'
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import PickHubScriptQuick from '../pickers/PickHubScriptQuick.svelte'
import { type Script, type FlowModule } from '$lib/gen'
import ListFiltersQuick from '$lib/components/home/ListFiltersQuick.svelte'
import { Folder, User } from 'lucide-svelte'
import type { FlowCopilotContext, FlowCopilotModule } from '../../copilot/flow'
import type { FlowEditorContext } from '../../flows/types'
import { copilotInfo } from '$lib/stores'
import { nextId } from '../../flows/flowModuleNextId'
import { twMerge } from 'tailwind-merge'
import { fade } from 'svelte/transition'
import { flip } from 'svelte/animate'
import Scrollable from '$lib/components/Scrollable.svelte'
import { Button } from '$lib/components/common'
import { SettingsIcon } from 'lucide-svelte'
import DefaultScriptsInner from '$lib/components/DefaultScriptsInner.svelte'
import GenAiQuick from './GenAiQuick.svelte'
import FlowToplevelNode from '../pickers/FlowToplevelNode.svelte'
const dispatch = createEventDispatcher()
export let summary: string | undefined = undefined
export let filter = ''
export let disableAi = false
export let preFilter: 'all' | 'workspace' | 'hub' = 'hub'
export let funcDesc: string
export let index: number
export let modules: FlowModule[]
export let owners: string[] = []
export let loading = false
export let small = false
export let kind: 'trigger' | 'script' | 'preprocessor' | 'failure' | 'approval'
export let selectedKind: 'script' | 'flow' | 'approval' | 'trigger' | 'preprocessor' | 'failure' =
kind
let lang: FlowCopilotModule['lang'] = undefined
let selectedCompletion: FlowCopilotModule['selectedCompletion'] = undefined
let filteredWorkspaceItems: (Script & { marked?: string })[] = []
let hubCompletions: FlowCopilotModule['hubCompletions'] = []
const { flowStore, flowStateStore, insertButtonOpen } =
getContext<FlowEditorContext>('FlowEditorContext')
const { modulesStore: copilotModulesStore, genFlow } =
getContext<FlowCopilotContext>('FlowCopilotContext')
let selected: { kind: 'owner' | 'integrations'; name: string | undefined } | undefined = undefined
let integrations: string[] = []
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
$: langs = processLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
.map((l) => [defaultScriptLanguages[l], l])
.filter(
(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1])
) as [string, SupportedLanguage | 'docker'][]
function displayLang(
lang: SupportedLanguage | 'docker',
kind: 'script' | 'flow' | 'approval' | 'trigger' | 'preprocessor' | 'failure'
) {
if (kind == 'trigger') {
return ['python3', 'bun', 'deno', 'go'].includes(lang)
} else if (kind == 'script') {
return true
} else if (kind == 'approval') {
return ['python3', 'bun', 'deno'].includes(lang)
} else if (kind == 'flow') {
return false
} else if (kind == 'preprocessor') {
return ['python3', 'bun', 'deno'].includes(lang)
} else if (kind == 'failure') {
return ['python3', 'bun', 'deno', 'go'].includes(lang)
}
}
async function onGenerate() {
if (!selectedCompletion && !$copilotInfo.exists_openai_resource_path) {
sendUserToast(
'Windmill AI is not enabled, you can activate it in the workspace settings',
true
)
return
}
$copilotModulesStore = [
{
id: nextId($flowStateStore, $flowStore),
type: selectedKind == 'trigger' ? 'trigger' : 'script',
description: funcDesc,
code: '',
source: selectedCompletion ? 'hub' : 'custom',
hubCompletions,
selectedCompletion,
editor: undefined,
lang
}
]
genFlow?.(index, modules, true)
dispatch('close')
}
let openScriptSettings = false
let selectedByKeyboard = 0
$: onSelectedKindChange(selectedKind)
function onSelectedKindChange(
_selectedKind: 'script' | 'flow' | 'approval' | 'trigger' | 'preprocessor' | 'failure'
) {
selectedByKeyboard = 0
}
let inlineScripts: [string, SupportedLanguage | 'docker'][] = []
const enterpriseLangs = ['bigquery', 'snowflake', 'mssql']
function computeInlineScriptChoices(
funcDesc: string,
selected: { kind: 'owner' | 'integrations'; name: string | undefined } | undefined,
preFilter: 'all' | 'workspace' | 'hub',
selectedKind: 'script' | 'flow' | 'approval' | 'trigger' | 'preprocessor' | 'failure'
) {
if (['script', 'trigger', 'failure', 'approval', 'preprocessor'].includes(selectedKind)) {
if (!selected && preFilter == 'all') {
inlineScripts = langs.filter((lang) => {
return (
(customUi?.languages == undefined || customUi?.languages?.includes(lang?.[1])) &&
(funcDesc?.length == 0 ||
lang?.[0]?.toLowerCase()?.includes(funcDesc?.toLowerCase())) &&
displayLang(lang?.[1], selectedKind)
)
})
return
}
}
inlineScripts = []
}
const allToplevelNodes: [string, string][] = [
['For Loop', 'forloop'],
['While Loop', 'whileloop'],
['Branch to one', 'branchone'],
['Branch to all', 'branchall']
]
let topLevelNodes: [string, string][] = []
function computeToplevelNodeChoices(funcDesc: string, preFilter: 'all' | 'workspace' | 'hub') {
if (funcDesc.length > 0 && preFilter == 'all' && kind == 'script') {
topLevelNodes = allToplevelNodes.filter((node) =>
node[0].toLowerCase().startsWith(funcDesc.toLowerCase())
)
} else {
topLevelNodes = []
}
}
$: computeToplevelNodeChoices(funcDesc, preFilter)
$: computeInlineScriptChoices(funcDesc, selected, preFilter, selectedKind)
$: onPrefilterChange(preFilter)
function onPrefilterChange(preFilter: 'all' | 'workspace' | 'hub') {
if (preFilter == 'workspace') {
hubCompletions = []
} else if (preFilter == 'hub') {
filteredWorkspaceItems = []
}
selectedByKeyboard = 0
}
$: aiLength =
funcDesc?.length > 0 && !disableAi && selectedKind != 'flow' && preFilter == 'all' ? 2 : 0
let scrollable: Scrollable | undefined
function onKeyDown(e: KeyboardEvent) {
let length =
topLevelNodes?.length +
inlineScripts.length +
aiLength +
filteredWorkspaceItems.length +
hubCompletions.length
if (e.key === 'ArrowDown') {
selectedByKeyboard = (selectedByKeyboard + 1) % length
scrollable?.scrollIntoView(selectedByKeyboard * 32)
e.preventDefault()
} else if (e.key === 'ArrowUp') {
selectedByKeyboard = (selectedByKeyboard - 1 + length) % length
scrollable?.scrollIntoView(selectedByKeyboard * 32)
e.preventDefault()
}
}
onMount(() => {
$insertButtonOpen = true
})
onDestroy(() => {
$insertButtonOpen = false
})
</script>
<svelte:window on:keydown={onKeyDown} />
<div class="flex flex-row grow min-w-0 divide-x relative {!small ? 'shadow-inset' : ''}">
{#if selectedKind != 'preprocessor'}
<Scrollable shiftedShadow scrollableClass="w-32 grow-0 shrink-0 ">
{#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)}
{#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'}
{#if preFilter !== 'workspace'}
<div class="pb-0 text-2xs font-light text-secondary ml-2">Workspace Folders</div>
{/if}
{#if owners.length > 0}
{#each owners as owner (owner)}
<div
in:fade={{ duration: 50 }}
animate:flip={{ duration: 100 }}
class="w-full px-0.5"
>
<button
class={twMerge(
'w-full text-left text-2xs text-primary font-normal py-2 px-3 hover:bg-surface-hover transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
owner === selected?.name ? 'bg-surface-hover' : ''
)}
on:click={() => {
selected = selected?.name == owner ? undefined : { kind: 'owner', name: owner }
}}
>
{#if owner.startsWith('f/')}
<Folder class="mr-0.5" size={14} />
{:else}
<User class="mr-0.5" size={14} />
{/if}
{owner.slice(2)}
</button>
</div>
{/each}
<div class="pb-1.5" />
{:else}
<div class="text-2xs text-tertiary font-light text-center py-3 px-3 items-center">
No items found.
</div>
{/if}
{/if}
{#if preFilter === 'hub' || preFilter === 'all'}
{#if preFilter == 'all'}
<div class="pb-0 text-2xs font-light text-secondary ml-2 pt-0.5">Integrations</div>
{/if}
<ListFiltersQuick
on:selected={() => {
filteredWorkspaceItems = []
selectedByKeyboard = 0
}}
filters={integrations}
bind:selectedFilter={selected}
resourceType
/>
{/if}
{:else if selectedKind === 'flow'}
{#if owners.length > 0}
{#each owners as owner (owner)}
<div in:fade={{ duration: 50 }} animate:flip={{ duration: 100 }}>
<button
class={twMerge(
'w-full text-left text-2xs text-primary font-normal py-2 px-3 hover:bg-surface-hover transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
owner === selected?.name ? 'bg-surface-hover' : ''
)}
on:click={() => {
selected = selected?.name == owner ? undefined : { kind: 'owner', name: owner }
}}
>
{#if owner.startsWith('f/')}
<Folder class="mr-0.5" size={14} />
{:else}
<User class="mr-0.5" size={14} />
{/if}
{owner.slice(2)}
</button>
</div>
{/each}
{/if}
{/if}
</Scrollable>
{/if}
<Scrollable bind:this={scrollable} scrollableClass="grow min-w-0">
{#if kind == 'script'}
{#each topLevelNodes as [label, kind], i (label)}
<FlowToplevelNode
on:click={() => {
dispatch('new', { kind })
}}
{label}
selected={selectedByKeyboard === i}
/>
{/each}
{/if}
{#if inlineScripts?.length > 0}
<div class="pb-0 flex flex-row items-center gap-2">
<div class=" text-2xs font-light text-secondary ml-2"
>New {selectedKind != 'script' ? selectedKind + ' ' : ''}script</div
>
{#if $userStore?.is_admin || $userStore?.is_super_admin}
{#if !openScriptSettings}
<Button
on:click={() => (openScriptSettings = true)}
startIcon={{ icon: SettingsIcon }}
color="light"
size="xs2"
btnClasses="!text-tertiary"
variant="contained"
title="Edit global default scripts"
/>
{:else}
<Button
on:click={() => (openScriptSettings = false)}
startIcon={{ icon: SettingsIcon }}
color="dark"
size="xs2"
variant="contained"
>
Close
</Button>
{/if}
{/if}
</div>
{#if openScriptSettings}
<div class="p-2">
<DefaultScriptsInner small />
</div>
{/if}
{#each inlineScripts as [label, lang], i (lang)}
<FlowScriptPickerQuick
eeRestricted={!$enterpriseLicense && enterpriseLangs.includes(lang)}
selected={selectedByKeyboard === i + topLevelNodes.length}
{enterpriseLangs}
{label}
lang={lang == 'docker' ? 'bash' : lang}
on:click={() => {
if (lang == 'docker') {
if (isCloudHosted()) {
sendUserToast(
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
true,
[
{
label: 'Learn more',
callback: () => {
window.open('https://www.windmill.dev/docs/advanced/docker', '_blank')
}
}
]
)
return
}
}
dispatch('new', {
kind: 'script',
inlineScript: {
language: lang == 'docker' ? 'bash' : lang,
kind: selectedKind,
subkind:
lang == 'docker'
? 'docker'
: selectedKind == 'preprocessor'
? 'preprocessor'
: 'flow',
summary
}
})
}}
/>
{/each}
{/if}
{#if !disableAi && funcDesc?.length > 0 && kind != 'failure' && kind != 'preprocessor' && (selectedKind == 'script' || selectedKind == 'trigger') && preFilter == 'all'}
<ul class="transition-all">
<li
><GenAiQuick
{funcDesc}
lang="TypeScript"
selected={selectedByKeyboard === inlineScripts?.length + topLevelNodes.length}
on:click={() => {
lang = 'bun'
onGenerate()
}}
/>
</li>
<li>
<GenAiQuick
{funcDesc}
lang="Python"
selected={selectedByKeyboard === inlineScripts?.length + topLevelNodes.length + 1}
on:click={() => {
lang = 'python3'
onGenerate()
}}
/>
</li>
</ul>
{/if}
{#if (!selected || selected?.kind === 'owner') && (preFilter === 'workspace' || preFilter === 'all')}
{#if !selected && (preFilter !== 'workspace' || funcDesc?.length > 0)}
<div class="pt-2 pb-0 text-2xs font-light text-secondary ml-2">Workspace</div>
{/if}
<WorkspaceScriptPickerQuick
bind:owners
bind:ownerFilter={selected}
bind:filteredWithOwner={filteredWorkspaceItems}
{filter}
kind={selectedKind}
selected={selectedByKeyboard - inlineScripts?.length - aiLength - topLevelNodes.length}
on:pickScript
on:pickFlow
/>
{/if}
{#if selectedKind != 'preprocessor' && selectedKind != 'flow'}
{#if (!selected || selected?.kind === 'integrations') && (preFilter === 'hub' || preFilter === 'all')}
{#if !selected && preFilter !== 'hub'}
<div class=" pb-0 text-2xs font-light text-secondary ml-2">Hub</div>
{/if}
<PickHubScriptQuick
bind:items={hubCompletions}
bind:filter
bind:apps={integrations}
appFilter={selected?.name}
kind={selectedKind}
selected={selectedByKeyboard -
inlineScripts?.length -
aiLength -
filteredWorkspaceItems?.length -
topLevelNodes.length}
on:pickScript
bind:loading
/>
{/if}
{/if}
</Scrollable>
</div>
<style>
.shadow-inset::before {
box-shadow: inset 25px 0px 12px -30px rgba(94, 129, 172, 0.5);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
pointer-events: none;
}
</style>

View File

@@ -1,33 +0,0 @@
<script lang="ts">
import { Wand2 } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
export let funcDesc: string
export let selected: boolean
export let lang: string
const dispatch = createEventDispatcher()
const onKeyDown = (e: KeyboardEvent) => {
if (selected && e.key === 'Enter') {
e.preventDefault()
dispatch('click')
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
<button
class="px-3 py-2 gap-2 w-full text-left hover:bg-surface-hover flex flex-row items-center transition-all rounded-md {selected
? 'bg-surface-hover'
: ''}"
on:click
>
<Wand2 size={14} class="text-violet-800 dark:text-violet-400" />
<span class="grow truncate text-left text-2xs text-primary font-normal">
Generate "{funcDesc}" in {lang}
</span>
{#if selected}
<kbd class="!text-xs">&crarr;</kbd>
{/if}
</button>

View File

@@ -3,52 +3,37 @@
import { getContext } from 'svelte'
import { classNames, emptySchema } from '$lib/utils'
import type { FlowModuleState } from '../flowState'
import Toggle from '$lib/components/Toggle.svelte'
import { NEVER_TESTED_THIS_FAR } from '../models'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import { fade } from 'svelte/transition'
import { Bug, X } from 'lucide-svelte'
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
import { createInlineScriptModule, pickScript } from '$lib/components/flows/flowStateUtils'
import type { FlowModule, RawScript } from '$lib/gen'
import { twMerge } from 'tailwind-merge'
import { Bug } from 'lucide-svelte'
export let small: boolean
const { selectedId, flowStateStore, flowStore } =
getContext<FlowEditorContext>('FlowEditorContext')
async function insertNewFailureModule(
inlineScript?: {
language: RawScript['language']
subkind: 'pgsql' | 'flow'
},
wsScript?: { path: string; summary: string; hash: string | undefined }
) {
var module: FlowModule = {
id: 'failure',
value: { type: 'identity' }
}
var state: FlowModuleState = {
schema: emptySchema(),
previewResult: NEVER_TESTED_THIS_FAR
}
function onToggle() {
if ($flowStore?.value?.failure_module) {
$flowStore.value.failure_module = undefined
// By default, we return to settings when disabling the failure module
$selectedId = 'settings-metadata'
} else {
const failureModule: FlowModuleState = {
schema: emptySchema(),
previewResult: NEVER_TESTED_THIS_FAR
}
if (inlineScript) {
;[module, state] = await createInlineScriptModule(
inlineScript.language,
'failure',
inlineScript.subkind,
'failure'
)
} else if (wsScript) {
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
$flowStore.value.failure_module = {
id: 'failure',
value: { type: 'identity' }
}
$flowStateStore['failure'] = failureModule
$selectedId = 'failure'
$flowStore = $flowStore
}
$flowStore.value.failure_module = module
$flowStateStore[module.id] = state
$selectedId = 'failure'
$flowStore = $flowStore
}
const { currentStepStore: copilotCurrentStepStore } =
@@ -57,71 +42,45 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={classNames(
'z-10',
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : 'cursor-pointer',
'border transition-colors duration-[400ms] ease-linear rounded-sm px-2 py-1 gap-2 bg-surface text-sm flex items-center flex-row',
$selectedId?.includes('failure')
? 'outline outline-offset-1 outline-2 outline-slate-900 dark:outline-slate-900/0 dark:bg-surface-secondary dark:border-gray-400'
: ''
)}
style="min-width: {small ? '200px' : '230px'}; max-width: 275px;"
on:click={() => {
if ($copilotCurrentStepStore !== undefined) return
if ($flowStore?.value?.failure_module) {
$selectedId = 'failure'
} else {
onToggle()
}
}}
class={classNames(
'z-10',
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : 'cursor-pointer',
'border transition-colors duration-[400ms] ease-linear rounded-sm px-2 py-1 bg-surface text-sm flex justify-between items-center flex-row overflow-x-hidden relative',
$selectedId?.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900 dark:outline-slate-900/0 dark:bg-surface-secondary dark:border-gray-400' : ''
)}
style={small ? 'min-width: 200px' : 'min-width: 275px'}
>
{#if $copilotCurrentStepStore !== undefined}
<div transition:fade class="absolute inset-0 bg-gray-500 bg-opacity-75 z-[900]" />
{/if}
<div class="flex items-center grow-0 min-w-0 gap-2">
<Bug size={16} color={$flowStore?.value?.failure_module ? '#3b82f6' : '#9CA3AF'} />
<div class=" flex justify-between items-center flex-wrap gap-2">
<Bug size={16} />
<span class="font-bold text-xs">Error Handler</span>
</div>
{#if !$flowStore?.value?.failure_module}
<div class="grow text-center font-bold text-xs">Error Handler</div>
{:else}
<div class="truncate grow min-w-0 text-center text-xs">
{$flowStore.value.failure_module?.summary ||
($flowStore.value.failure_module?.value.type === 'rawscript'
? `${$flowStore.value.failure_module?.value.language}`
: 'TBD')}
</div>
{/if}
{#if !$flowStore?.value?.failure_module}
<InsertModuleButton
disableAi={false}
index={0}
placement={'top-center'}
on:new={(e) => {
insertNewFailureModule(e.detail.inlineScript)
}}
on:pickScript={(e) => {
insertNewFailureModule(undefined, e.detail)
}}
kind="failure"
/>
{:else}
<button
title="Delete failure script"
type="button"
class={twMerge(
'w-5 h-5 flex items-center justify-center grow-0 shrink-0',
'outline-[1px] outline dark:outline-gray-500 outline-gray-300',
'text-secondary',
'bg-surface focus:outline-none hover:bg-surface-hover rounded '
)}
on:click={() => {
$flowStore.value.failure_module = undefined
$selectedId = 'settings-metadata'
}}
>
<X size={12} />
</button>
{/if}
<div class=" items-center truncate flex text-xs">
{#if Boolean($flowStore?.value?.failure_module)}
<span>
{$flowStore.value.failure_module?.summary ||
($flowStore.value.failure_module?.value.type === 'rawscript'
? `${$flowStore.value.failure_module?.value.language}`
: 'TBD')}
</span>
{/if}
</div>
<Toggle
size={small ? 'xs' : 'sm'}
checked={Boolean($flowStore?.value?.failure_module)}
on:change={onToggle}
id="error-handler-toggle"
/>
</div>

View File

@@ -2,17 +2,15 @@
import type { FlowEditorContext } from '../types'
import { createEventDispatcher, getContext, tick } from 'svelte'
import {
createInlineScriptModule,
createBranchAll,
createBranches,
createLoop,
createWhileLoop,
deleteFlowStateById,
emptyModule,
pickScript,
pickFlow
pickScript
} from '$lib/components/flows/flowStateUtils'
import type { FlowModule, RawScript, Script } from '$lib/gen'
import type { FlowModule } from '$lib/gen'
import { emptyFlowModuleState, initFlowStepWarnings } from '../utils'
import FlowSettingsItem from './FlowSettingsItem.svelte'
import FlowConstantsItem from './FlowConstantsItem.svelte'
@@ -33,6 +31,8 @@
import { tutorialInProgress } from '$lib/tutorialUtils'
import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte'
import { replaceId } from '../flowStore'
import { emptySchema } from '$lib/utils'
import { NEVER_TESTED_THIS_FAR } from '../models'
export let modules: FlowModule[] | undefined
export let sidebarSize: number | undefined = undefined
@@ -61,22 +61,12 @@
| 'trigger'
| 'approval'
| 'end',
wsScript?: { path: string; summary: string; hash: string | undefined },
wsFlow?: { path: string; summary: string },
inlineScript?: {
language: RawScript['language']
kind: Script['kind']
subkind: 'pgsql' | 'flow'
id: string
summary?: string
}
wsScript?: { path: string; summary: string; hash: string | undefined }
): Promise<FlowModule[]> {
push(history, $flowStore)
var module = emptyModule($flowStateStore, $flowStore, kind == 'flow')
var state = emptyFlowModuleState()
if (wsFlow) {
;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id)
} else if (wsScript) {
if (wsScript) {
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
} else if (kind == 'forloop') {
;[module, state] = await createLoop(
@@ -99,49 +89,11 @@
module.summary = 'Terminate flow'
module.stop_after_if = { skip_if_stopped: false, expr: 'true' }
}
if (inlineScript) {
const { language, kind, subkind } = inlineScript
;[module, state] = await createInlineScriptModule(
language,
kind,
subkind,
module.id,
module.summary
)
}
if (!modules) return [module]
modules.splice(index, 0, module)
return modules
}
async function insertNewPreprocessorModule(
inlineScript?: {
language: RawScript['language']
subkind: 'pgsql' | 'flow'
},
wsScript?: { path: string; summary: string; hash: string | undefined }
) {
var module: FlowModule = {
id: 'preprocessor',
value: { type: 'identity' }
}
var state = emptyFlowModuleState()
if (inlineScript) {
;[module, state] = await createInlineScriptModule(
inlineScript.language,
'script',
inlineScript.subkind,
'preprocessor'
)
} else if (wsScript) {
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
}
$flowStore.value.preprocessor_module = module
$flowStateStore[module.id] = state
}
function removeAtId(modules: FlowModule[], id: string): FlowModule[] {
const index = modules.findIndex((mod) => mod.id == id)
if (index != -1) {
@@ -359,16 +311,22 @@
$moving = undefined
} else {
if (detail.detail === 'preprocessor') {
insertNewPreprocessorModule(detail.inlineScript, detail.script)
const preprocessorModule = {
schema: emptySchema(),
previewResult: NEVER_TESTED_THIS_FAR
}
$flowStore.value.preprocessor_module = {
id: 'preprocessor',
value: { type: 'identity' }
}
$flowStateStore['preprocessor'] = preprocessorModule
$selectedId = 'preprocessor'
} else {
await insertNewModuleAtIndex(
detail.modules,
detail.index ?? 0,
detail.kind,
detail.script,
detail.flow,
detail.inlineScript
detail.detail,
detail.script
)
$selectedId = detail.modules[detail.index ?? 0].id
}

View File

@@ -1,200 +1,169 @@
<script lang="ts">
import { Menu } from '$lib/components/common'
import { createEventDispatcher, getContext } from 'svelte'
import { Cross, Zap } from 'lucide-svelte'
import StepGenQuick from '$lib/components/copilot/StepGenQuick.svelte'
import FlowInputsQuick from '../content/FlowInputsQuick.svelte'
import { CheckCircle2, Code, Cross, GitBranch, Repeat, Square, Zap } from 'lucide-svelte'
import StepGen from '$lib/components/copilot/StepGen.svelte'
import type { FlowModule } from '$lib/gen'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import ToggleHubWorkspaceQuick from '$lib/components/ToggleHubWorkspaceQuick.svelte'
import { twMerge } from 'tailwind-merge'
import type { ComputeConfig } from 'svelte-floating-ui'
import TopLevelNode from '../pickers/TopLevelNode.svelte'
import PopupV2 from '$lib/components/common/popup/PopupV2.svelte'
import { flip, offset } from 'svelte-floating-ui/dom'
// import type { Writable } from 'svelte/store'
const dispatch = createEventDispatcher()
export let trigger = false
export let stop = false
export let index: number = 0
export let open: boolean | undefined = undefined
export let index: number
export let funcDesc = ''
export let modules: FlowModule[] = []
export let modules: FlowModule[]
export let disableAi = false
export let kind: 'script' | 'trigger' | 'preprocessor' | 'failure' = 'script'
export let allowTrigger = true
type Alignment = 'start' | 'end' | 'center'
type Side = 'top' | 'bottom'
type Placement = `${Side}-${Alignment}`
export let placement: Placement = 'bottom-center'
let floatingConfig: ComputeConfig = {
strategy: 'fixed',
// @ts-ignore
placement,
middleware: [offset(8), flip()],
autoUpdate: true
}
$: !open && (funcDesc = '')
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
let selectedKind: 'script' | 'trigger' | 'preprocessor' | 'approval' | 'flow' | 'failure' = kind
let preFilter: 'all' | 'workspace' | 'hub' = 'all'
let loading = false
let small = false
let open = false
$: small = kind === 'preprocessor' || kind === 'failure'
</script>
<!-- <Menu transitionDuration={0} pointerDown bind:show={open} noMinW {placement} let:close> -->
<!-- {floatingConfig}
floatingClasses="mt-2"
containerClasses="border rounded-lg shadow-lg bg-surface"
noTransition
shouldUsePortal={true} -->
<PopupV2 {floatingConfig} bind:open let:close target="#flow-editor">
<svelte:fragment let:pointerdown let:pointerup slot="button">
<Menu
transitionDuration={0}
pointerDown
bind:show={open}
noMinW
placement="bottom-center"
let:close
>
<svelte:fragment slot="trigger">
<button
title={`Add ${
kind === 'failure'
? ' failure module '
: kind === 'preprocessor'
? 'preprocessor step'
: kind === 'trigger'
? 'trigger'
: 'step'
}`}
title="Add step"
id={`flow-editor-add-step-${index}`}
type="button"
class={twMerge(
'w-5 h-5 flex items-center justify-center',
'outline-[1px] outline dark:outline-gray-500 outline-gray-300',
'text-secondary',
'bg-surface focus:outline-none hover:bg-surface-hover rounded '
'bg-surface focus:outline-none hover:bg-surface-hover rounded '
)}
on:pointerdown|preventDefault|stopPropagation={pointerdown}
on:pointerup={pointerup}
>
{#if kind === 'trigger'}
<Zap size={12} />
{:else}
<Cross size={12} />
{/if}
<Cross size={12} />
</button>
</svelte:fragment>
<!-- FOO -->
<div
id="flow-editor-insert-module"
class="flex flex-col h-[400px] {small ? 'w-[450px]' : 'w-[650px]'} pt-1 pr-1 pl-1 gap-1.5"
on:wheel={(e) => {
e.stopPropagation()
}}
role="none"
>
<div class="flex flex-row items-center gap-2">
<StepGenQuick {disableAi} on:insert bind:funcDesc {preFilter} {loading} />
{#if selectedKind != 'preprocessor' && selectedKind != 'flow'}
<ToggleHubWorkspaceQuick bind:selected={preFilter} />
{/if}
</div>
<div id="flow-editor-insert-module">
<StepGen on:insert {index} bind:funcDesc bind:open {close} {modules} {disableAi} />
<div class="flex flex-row grow min-h-0">
{#if kind === 'script' || kind == 'trigger'}
<div class="flex-none flex flex-col text-xs text-primary">
<TopLevelNode
label="Action"
selected={selectedKind === 'script'}
on:select={() => {
selectedKind = 'script'
{#if funcDesc.length === 0}
<div class="font-mono divide-y text-xs w-full text-secondary">
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'script')
}}
role="menuitem"
tabindex="-1"
>
<Code size={14} />
Action
</button>
{#if customUi?.triggers != false && trigger}
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'trigger')
}}
/>
{#if customUi?.triggers != false && allowTrigger}
<TopLevelNode
label="Trigger"
selected={selectedKind === 'trigger'}
on:select={() => {
selectedKind = 'trigger'
}}
/>
{/if}
<TopLevelNode
label="Approval/Prompt"
selected={selectedKind === 'approval'}
on:select={() => {
selectedKind = 'approval'
}}
/>
{#if customUi?.flowNode != false}
<TopLevelNode
label="Flow"
selected={selectedKind === 'flow'}
on:select={() => {
selectedKind = 'flow'
}}
/>
{/if}
{#if stop}
<TopLevelNode
label="End Flow"
selected={selectedKind === 'script'}
on:select={() => {
selectedKind = 'script'
}}
/>
{/if}
role="menuitem"
tabindex="-1"
>
<Zap size={14} />
Trigger
</button>
{/if}
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'approval')
}}
role="menuitem"
tabindex="-1"
>
<CheckCircle2 size={14} />
Approval/Prompt
</button>
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'forloop')
}}
role="menuitem"
>
<Repeat size={14} />
<TopLevelNode
label="For Loop"
on:select={() => {
close(null)
dispatch('new', { kind: 'forloop' })
}}
/>
<TopLevelNode
label="While Loop"
on:select={() => {
close(null)
dispatch('new', { kind: 'whileloop' })
}}
/>
<TopLevelNode
label="Branch to one"
on:select={() => {
close(null)
dispatch('new', { kind: 'branchone' })
}}
/>
<TopLevelNode
label="Branch to all"
on:select={() => {
close(null)
dispatch('new', { kind: 'branchall' })
}}
/>
</div>
{/if}
For Loop
</button>
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'whileloop')
}}
role="menuitem"
>
<Repeat size={14} />
<FlowInputsQuick
{selectedKind}
bind:loading
filter={funcDesc}
{modules}
{index}
{disableAi}
{funcDesc}
{kind}
on:close={() => {
close(null)
}}
on:new
on:pickScript
on:pickFlow
{preFilter}
{small}
/>
</div>
While Loop
</button>
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'branchone')
}}
role="menuitem"
>
<GitBranch size={14} />
Branch to one
</button>
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'branchall')
}}
role="menuitem"
>
<GitBranch size={14} />
Branch to all
</button>
{#if customUi?.flowNode != false}
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover rounded-none whitespace-nowrap flex flex-row gap-2 items-center"
on:pointerdown={() => {
close()
dispatch('new', 'flow')
}}
role="menuitem"
>
<BarsStaggered size={14} />
Flow
</button>
{/if}
{#if stop}
<button
class="w-full text-left py-2 px-3 hover:bg-surface-hover inline-flex gap-2.5"
on:pointerdown={() => {
close()
dispatch('new', 'end')
}}
role="menuitem"
>
<Square size={14} />
End Flow
</button>
{/if}
</div>
{/if}
</div>
</PopupV2>
</Menu>

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