mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 00:03:02 +00:00
Merge remote-tracking branch 'origin/main' into fr/flow-triggers-v0
# Conflicts: # frontend/src/lib/components/FlowBuilder.svelte # frontend/src/lib/components/graph/renderers/nodes/InputNode.svelte
This commit is contained in:
@@ -43,6 +43,9 @@ 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
|
||||
|
||||
@@ -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
|
||||
--all -- --nocapture
|
||||
DISABLE_EMBEDDING=true RUST_LOG=info cargo test --features
|
||||
enterprise,deno_core --all -- --nocapture
|
||||
|
||||
@@ -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
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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: 90
|
||||
run: |
|
||||
vcpkg.exe install openssl-windows:x64-windows
|
||||
vcpkg.exe install openssl:x64-windows-static
|
||||
vcpkg.exe integrate install
|
||||
$env:VCPKGRS_DYNAMIC=1
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
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,deno_core
|
||||
|
||||
- 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
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect
|
||||
features=embedding,parquet,openidconnect,deno_core
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
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
|
||||
@@ -75,7 +77,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -136,7 +138,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -198,7 +200,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
|
||||
@@ -1,5 +1,94 @@
|
||||
# Changelog
|
||||
|
||||
## [1.405.5](https://github.com/windmill-labs/windmill/compare/v1.405.4...v1.405.5) (2024-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* windows.exe build with github workflow doesn't have openssl.dll bundled in ([#4489](https://github.com/windmill-labs/windmill/issues/4489)) ([284cb40](https://github.com/windmill-labs/windmill/commit/284cb4069c97efe59b5caf3effb68c8b30e02b73))
|
||||
|
||||
## [1.405.4](https://github.com/windmill-labs/windmill/compare/v1.405.3...v1.405.4) (2024-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** correctly initialize step inputs on new inline script ([289ad51](https://github.com/windmill-labs/windmill/commit/289ad51374f0344582372572fc521f3b2bf12b33))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix new instance db setup ([73ab8e1](https://github.com/windmill-labs/windmill/commit/73ab8e1653d6e0c0c69fa7dcd96583f25d13ef86))
|
||||
|
||||
## [1.403.0](https://github.com/windmill-labs/windmill/compare/v1.402.3...v1.403.0) (2024-10-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* flow step skipping ([#4461](https://github.com/windmill-labs/windmill/issues/4461)) ([0df169e](https://github.com/windmill-labs/windmill/commit/0df169e3f996ed54b91569b13cce15d7d019a213))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip one migration to avoid using md5 for azure support ([630ae5d](https://github.com/windmill-labs/windmill/commit/630ae5d425cd9957d674befd2df96e2befec52a3))
|
||||
|
||||
## [1.402.3](https://github.com/windmill-labs/windmill/compare/v1.402.2...v1.402.3) (2024-09-30)
|
||||
|
||||
|
||||
|
||||
@@ -158,6 +158,9 @@ 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/*
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT job_kind = 'identity' FROM completed_job WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "829130d74c107e4e1a86f3e772657ae030463c139c39072777e453bfb7e9c0c3"
|
||||
}
|
||||
Generated
+135
-218
@@ -14,9 +14,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.1"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
@@ -40,22 +40,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.3.0",
|
||||
"cipher",
|
||||
"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"
|
||||
@@ -187,19 +176,6 @@ 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"
|
||||
@@ -209,7 +185,7 @@ dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"password-hash 0.5.0",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -383,7 +359,7 @@ dependencies = [
|
||||
"arrow-schema",
|
||||
"chrono",
|
||||
"half",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"lexical-core",
|
||||
"num",
|
||||
"serde",
|
||||
@@ -510,11 +486,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa"
|
||||
checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"brotli 7.0.0",
|
||||
"bzip2",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
@@ -595,7 +571,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
@@ -643,9 +619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
@@ -654,9 +630,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1334,7 +1310,7 @@ dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq 0.3.1",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1363,7 +1339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"cipher 0.3.0",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1420,6 +1396,17 @@ 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"
|
||||
@@ -1609,9 +1596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.22"
|
||||
version = "1.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0"
|
||||
checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -1712,16 +1699,6 @@ 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"
|
||||
@@ -1735,9 +1712,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.18"
|
||||
version = "4.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1745,9 +1722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.18"
|
||||
version = "4.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1898,12 +1875,6 @@ 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"
|
||||
@@ -2259,7 +2230,7 @@ dependencies = [
|
||||
"arrow-array",
|
||||
"arrow-ipc",
|
||||
"arrow-schema",
|
||||
"async-compression 0.4.12",
|
||||
"async-compression 0.4.13",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"bzip2",
|
||||
@@ -2282,7 +2253,7 @@ dependencies = [
|
||||
"glob",
|
||||
"half",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"num_cpus",
|
||||
@@ -2451,7 +2422,7 @@ dependencies = [
|
||||
"datafusion-expr",
|
||||
"datafusion-physical-expr",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"regex-syntax 0.8.5",
|
||||
@@ -2480,7 +2451,7 @@ dependencies = [
|
||||
"half",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"paste",
|
||||
@@ -2524,7 +2495,7 @@ dependencies = [
|
||||
"futures",
|
||||
"half",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -2708,7 +2679,7 @@ dependencies = [
|
||||
"dlopen2_derive",
|
||||
"once_cell",
|
||||
"rustls-native-certs 0.7.3",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls-pemfile 2.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2789,8 +2760,8 @@ checksum = "2b995ce6016bc4f05453c8632a9d42a58536c1cbb7ece09f1aa66dda3f8d474b"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"deno_native_certs",
|
||||
"rustls 0.23.13",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls 0.23.14",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-tokio-stream",
|
||||
"rustls-webpki 0.102.8",
|
||||
"serde",
|
||||
@@ -2929,7 +2900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher 0.3.0",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -3807,9 +3778,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "git-version"
|
||||
@@ -3906,7 +3877,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -3925,7 +3896,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -3974,6 +3945,12 @@ 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"
|
||||
@@ -4167,9 +4144,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.4"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
|
||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
@@ -4254,7 +4231,7 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"hyper 1.4.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -4379,12 +4356,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown 0.15.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -4407,15 +4384,6 @@ 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"
|
||||
@@ -4448,9 +4416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.10.0"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
|
||||
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
|
||||
|
||||
[[package]]
|
||||
name = "is-macro"
|
||||
@@ -4550,7 +4518,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4848,7 +4816,7 @@ version = "3.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c42f95f9d296f2dcb50665f507ed5a68a171453142663ce44d77a4eb217b053"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"aes",
|
||||
"base64 0.21.7",
|
||||
"block-modes",
|
||||
"crc-any",
|
||||
@@ -4887,7 +4855,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"gethostname",
|
||||
"mail-builder",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"rustls-pki-types",
|
||||
"smtp-proto",
|
||||
"tokio",
|
||||
@@ -5418,9 +5386,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.4"
|
||||
version = "0.36.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
|
||||
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5444,7 +5412,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"quick-xml 0.36.2",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"ring 0.17.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5695,7 +5663,7 @@ dependencies = [
|
||||
"arrow-schema",
|
||||
"arrow-select",
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
"brotli 6.0.0",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flate2",
|
||||
@@ -5725,17 +5693,6 @@ 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"
|
||||
@@ -5759,18 +5716,6 @@ 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"
|
||||
@@ -5821,25 +5766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"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",
|
||||
"indexmap 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6408,7 +6335,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"socket2 0.5.7",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -6425,7 +6352,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
@@ -6767,11 +6694,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
|
||||
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
|
||||
dependencies = [
|
||||
"async-compression 0.4.12",
|
||||
"async-compression 0.4.13",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
@@ -6794,9 +6721,9 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs 0.7.3",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls 0.23.14",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -7094,9 +7021,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.13"
|
||||
version = "0.23.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
|
||||
checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -7126,7 +7053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
@@ -7139,7 +7066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
@@ -7156,11 +7083,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.3"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
@@ -7177,7 +7103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
]
|
||||
@@ -7525,7 +7451,7 @@ version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -7621,15 +7547,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.9.0"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||
checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -7639,9 +7565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.9.0"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec"
|
||||
dependencies = [
|
||||
"darling 0.20.10",
|
||||
"proc-macro2",
|
||||
@@ -7655,7 +7581,7 @@ version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -8035,14 +7961,14 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rustls 0.23.13",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls 0.23.14",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
@@ -8412,7 +8338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b67e115ab136fe0eb03558bb0508ca7782eeb446a96d165508c48617e3fd94"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"swc_cached",
|
||||
@@ -8563,7 +8489,7 @@ checksum = "d37dc505c92af56d0f77cf6f31a6ccd37ac40cad1e01ff77277e0b1c70e8f8ff"
|
||||
dependencies = [
|
||||
"better_scoped_tls",
|
||||
"bitflags 2.6.0",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"once_cell",
|
||||
"phf",
|
||||
"rustc-hash 1.1.0",
|
||||
@@ -8632,7 +8558,7 @@ checksum = "446da32cac8299973aaf1d37496562bfd0c1e4f3c3ab5d0af6f07f42e8184102"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"dashmap",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"sha1",
|
||||
@@ -8671,7 +8597,7 @@ version = "0.130.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e62b199454a576c5fdbd7e1bef8ab88a395427456d8a713d994b7d469833aa"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"rustc-hash 1.1.0",
|
||||
@@ -9358,7 +9284,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
@@ -9454,7 +9380,7 @@ version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -9467,7 +9393,7 @@ version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -9541,7 +9467,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
dependencies = [
|
||||
"async-compression 0.4.12",
|
||||
"async-compression 0.4.13",
|
||||
"bitflags 2.6.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -9663,7 +9589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea6023f9fe4b69267ccd3ed7d203d931c43c5f82dbaa0f07202bc17193a5f43"
|
||||
dependencies = [
|
||||
"loki-api",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snap",
|
||||
@@ -9926,9 +9852,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id"
|
||||
@@ -9968,9 +9894,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
|
||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -10047,7 +9973,7 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"rustls 0.23.13",
|
||||
"rustls 0.23.14",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10429,7 +10355,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -10443,11 +10369,10 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"once_cell",
|
||||
"pg-embed",
|
||||
"prometheus",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"rsmq_async",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10471,7 +10396,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -10515,7 +10440,7 @@ dependencies = [
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"rsa 0.7.2",
|
||||
"rsmq_async",
|
||||
"rust-embed",
|
||||
@@ -10555,7 +10480,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
@@ -10573,7 +10498,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -10586,7 +10511,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -10604,7 +10529,7 @@ dependencies = [
|
||||
"hex",
|
||||
"hmac",
|
||||
"hyper 1.4.1",
|
||||
"indexmap 2.5.0",
|
||||
"indexmap 2.6.0",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
@@ -10613,7 +10538,7 @@ dependencies = [
|
||||
"prometheus",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
@@ -10631,7 +10556,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"rsmq_async",
|
||||
@@ -10646,7 +10571,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -10667,7 +10592,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -10676,7 +10601,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10688,7 +10613,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -10700,7 +10625,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10712,7 +10637,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -10723,7 +10648,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -10734,7 +10659,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -10752,7 +10677,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -10769,7 +10694,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10781,7 +10706,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10799,7 +10724,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.15",
|
||||
@@ -10820,7 +10745,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -10830,7 +10755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -10846,7 +10771,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"prometheus",
|
||||
"regex",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"rsmq_async",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10863,7 +10788,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -10873,7 +10798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -10913,13 +10838,14 @@ dependencies = [
|
||||
"prometheus",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest 0.12.7",
|
||||
"reqwest 0.12.8",
|
||||
"rsmq_async",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"sqlx",
|
||||
"swc_ecma_parser 0.144.3",
|
||||
"tar",
|
||||
"tiberius",
|
||||
"tokio",
|
||||
@@ -11296,18 +11222,9 @@ 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]]
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -27,7 +27,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.402.3"
|
||||
version = "1.405.5"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -47,7 +47,6 @@ 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"]
|
||||
@@ -57,6 +56,7 @@ 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,9 +85,8 @@ uuid.workspace = true
|
||||
gethostname.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_core = { workspace = true, optional = 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
|
||||
|
||||
|
||||
@@ -105,6 +104,7 @@ 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" }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
INSERT INTO workspace(id, name, owner) VALUES
|
||||
('admins', 'Admins', 'admin@windmill.dev');
|
||||
('admins', 'Admins', 'admin@windmill.dev') ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id) VALUES
|
||||
('admins');
|
||||
('admins') ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_key
|
||||
(workspace_id, kind, key)
|
||||
@@ -13,4 +13,4 @@ INSERT INTO workspace_key
|
||||
FROM generate_series(1, 32) -- generates 32 characters
|
||||
),
|
||||
''
|
||||
));
|
||||
)) ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_KIND ADD VALUE IF NOT EXISTS 'preprocessor';
|
||||
@@ -399,15 +399,11 @@ fn parse_ansible_options(opts: &Vec<Yaml>) -> AnsiblePlaybookOptions {
|
||||
if c > 0 && c <= 6 {
|
||||
ret.verbosity = Some("v".repeat(c.min(6)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,10 +418,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);
|
||||
}
|
||||
|
||||
+36
-16
@@ -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,
|
||||
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -92,9 +92,6 @@ 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
|
||||
@@ -118,6 +115,7 @@ where
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "deno_core")]
|
||||
deno_core::JsRuntime::init_platform(None);
|
||||
create_and_run_current_thread_inner(windmill_main())
|
||||
}
|
||||
@@ -137,6 +135,7 @@ 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}");
|
||||
@@ -168,7 +167,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,
|
||||
@@ -177,11 +176,31 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
|
||||
None,
|
||||
&job_dir,
|
||||
"cache_init",
|
||||
windmill_worker::get_common_bun_proc_envs(None).await,
|
||||
envs.clone(),
|
||||
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...");
|
||||
}
|
||||
@@ -341,14 +360,6 @@ 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");
|
||||
@@ -373,8 +384,16 @@ async fn windmill_main() -> anyhow::Result<()> {
|
||||
let is_agent = mode == Mode::Agent;
|
||||
|
||||
if !is_agent {
|
||||
// migration code to avoid break
|
||||
windmill_api::migrate_db(&db).await?;
|
||||
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...")
|
||||
}
|
||||
}
|
||||
|
||||
let (killpill_tx, mut killpill_rx) = tokio::sync::broadcast::channel::<()>(2);
|
||||
@@ -874,6 +893,7 @@ 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,
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.402.3
|
||||
version: 1.405.5
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -137,6 +137,17 @@ impl Migrate for CustomMigrator {
|
||||
"../../custom_migrations/create_workspace_without_md5.sql"
|
||||
))
|
||||
.await?;
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time )
|
||||
VALUES ( $1, $2, TRUE, $3, -1 ) ON CONFLICT DO NOTHING
|
||||
"#,
|
||||
)
|
||||
.bind(migration.version)
|
||||
.bind(&*migration.description)
|
||||
.bind(&*migration.checksum)
|
||||
.execute(&mut *self.inner)
|
||||
.await?;
|
||||
return Ok(std::time::Duration::from_secs(0));
|
||||
} else {
|
||||
let r = self.inner.apply(migration).await;
|
||||
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
@@ -19,6 +18,7 @@ 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,11 +293,8 @@ 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)]
|
||||
@@ -4130,7 +4127,7 @@ async fn run_dependencies_job(
|
||||
JsonRawValue::from_string("true".to_string()).unwrap(),
|
||||
);
|
||||
if language == ScriptLang::Bun {
|
||||
let annotation = windmill_common::worker::get_annotation(&raw_code);
|
||||
let annotation = windmill_common::worker::get_annotation_ts(&raw_code);
|
||||
hm.insert(
|
||||
"npm_mode".to_string(),
|
||||
JsonRawValue::from_string(annotation.npm_mode.to_string()).unwrap(),
|
||||
@@ -4683,8 +4680,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,
|
||||
@@ -5115,8 +5112,6 @@ async fn get_completed_job_result(
|
||||
Ok(Json(result).into_response())
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CountByTagQuery {
|
||||
horizon_secs: Option<i64>,
|
||||
@@ -5130,7 +5125,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>> {
|
||||
|
||||
@@ -58,7 +58,10 @@ 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))
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ use windmill_common::{
|
||||
utils::{
|
||||
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
|
||||
},
|
||||
worker::{get_annotation, to_raw_value},
|
||||
worker::{get_annotation_ts, 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(&ns.content);
|
||||
let anns = get_annotation_ts(&ns.content);
|
||||
if anns.native_mode {
|
||||
ScriptLang::Bunnative
|
||||
} else {
|
||||
|
||||
@@ -128,6 +128,7 @@ struct UntaggedFlowStatusModule {
|
||||
while_loop: Option<bool>,
|
||||
approvers: Option<Vec<Approval>>,
|
||||
failed_retries: Option<Vec<Uuid>>,
|
||||
skipped: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
@@ -179,6 +180,7 @@ pub enum FlowStatusModule {
|
||||
approvers: Vec<Approval>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
failed_retries: Vec<Uuid>,
|
||||
skipped: bool,
|
||||
},
|
||||
Failure {
|
||||
id: String,
|
||||
@@ -255,6 +257,7 @@ impl<'de> Deserialize<'de> for FlowStatusModule {
|
||||
branch_chosen: untagged.branch_chosen,
|
||||
approvers: untagged.approvers.unwrap_or_default(),
|
||||
failed_retries: untagged.failed_retries.unwrap_or_default(),
|
||||
skipped: untagged.skipped.unwrap_or(false),
|
||||
}),
|
||||
"Failure" => Ok(FlowStatusModule::Failure {
|
||||
id: untagged
|
||||
|
||||
@@ -303,14 +303,14 @@ fn parse_file<T: FromStr>(path: &str) -> Option<T> {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub struct Annotations {
|
||||
pub struct TypeScriptAnnotations {
|
||||
pub npm_mode: bool,
|
||||
pub nodejs_mode: bool,
|
||||
pub native_mode: bool,
|
||||
pub nobundling: bool,
|
||||
}
|
||||
|
||||
pub fn get_annotation(inner_content: &str) -> Annotations {
|
||||
pub fn get_annotation_ts(inner_content: &str) -> TypeScriptAnnotations {
|
||||
let annotations = inner_content
|
||||
.lines()
|
||||
.take_while(|x| x.starts_with("//"))
|
||||
@@ -324,7 +324,25 @@ pub fn get_annotation(inner_content: &str) -> Annotations {
|
||||
let nobundling: bool =
|
||||
annotations.contains(&"nobundling".to_string()) || nodejs_mode || *DISABLE_BUNDLING;
|
||||
|
||||
Annotations { npm_mode, nodejs_mode, native_mode, nobundling }
|
||||
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 }
|
||||
}
|
||||
|
||||
pub struct SqlAnnotations {
|
||||
@@ -449,10 +467,13 @@ 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(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::anyhow;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::error::Error;
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IndexReader;
|
||||
|
||||
@@ -18,6 +18,7 @@ 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
|
||||
@@ -59,15 +60,15 @@ once_cell.workspace = true
|
||||
rsmq_async.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
bit-vec.workspace = 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
|
||||
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 }
|
||||
postgres-native-tls.workspace = true
|
||||
native-tls.workspace = true
|
||||
mysql_async.workspace = true
|
||||
@@ -89,13 +90,17 @@ 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
|
||||
deno_webidl.workspace = true
|
||||
deno_web.workspace = true
|
||||
deno_console.workspace = true
|
||||
deno_url.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_net.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 }
|
||||
zstd.workspace = true
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
#[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(
|
||||
@@ -28,6 +37,7 @@ impl FetchPermissions for PermissionsContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
impl TimersPermission for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn allow_hrtime(&mut self) -> bool {
|
||||
@@ -35,6 +45,7 @@ impl TimersPermission for PermissionsContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
impl NetPermissions for PermissionsContainer {
|
||||
fn check_read(
|
||||
&mut self,
|
||||
@@ -61,12 +72,14 @@ 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());
|
||||
@@ -122,3 +135,6 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "deno_core"))]
|
||||
fn main() {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
os::unix::fs::PermissionsExt,
|
||||
@@ -5,6 +6,13 @@ 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;
|
||||
@@ -25,7 +33,7 @@ use crate::{
|
||||
OccupancyMetrics,
|
||||
},
|
||||
handle_child::handle_child,
|
||||
python_executor::{create_dependencies_dir, handle_python_reqs, pip_compile},
|
||||
python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile},
|
||||
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
|
||||
TZ_ENV,
|
||||
};
|
||||
@@ -72,7 +80,7 @@ async fn handle_ansible_python_deps(
|
||||
if requirements.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
pip_compile(
|
||||
uv_pip_compile(
|
||||
job_id,
|
||||
&requirements,
|
||||
mem_peak,
|
||||
@@ -82,6 +90,8 @@ async fn handle_ansible_python_deps(
|
||||
worker_name,
|
||||
w_id,
|
||||
&mut Some(occupancy_metrics),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -378,6 +388,7 @@ 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());
|
||||
|
||||
@@ -32,6 +32,9 @@ 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();
|
||||
@@ -226,13 +229,19 @@ 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(split_char)
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase()
|
||||
@@ -289,14 +298,26 @@ 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 = ($Env:PSModulePath -split ':')[-1]
|
||||
$env:PSModulePath = \"$PSHome/Modules\"
|
||||
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 {
|
||||
@@ -312,11 +333,29 @@ $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());
|
||||
@@ -355,10 +394,24 @@ $env:PSModulePath = \"{}:$PSModulePathBackup\"",
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?
|
||||
} else {
|
||||
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)
|
||||
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)
|
||||
.env_clear()
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
@@ -366,11 +419,53 @@ $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())
|
||||
.spawn()?
|
||||
.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()?
|
||||
};
|
||||
|
||||
handle_child(
|
||||
&job.id,
|
||||
db,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use std::{collections::HashMap, fs, io, path::Path, process::Stdio, time::Instant};
|
||||
#[cfg(feature = "deno_core")]
|
||||
use std::time::Instant;
|
||||
use std::{collections::HashMap, fs, io, path::Path, process::Stdio};
|
||||
|
||||
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;
|
||||
@@ -23,6 +29,9 @@ 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;
|
||||
@@ -38,7 +47,7 @@ use windmill_common::{
|
||||
get_latest_hash_for_path,
|
||||
jobs::{QueuedJob, PREPROCESSOR_FAKE_ENTRYPOINT},
|
||||
scripts::ScriptLang,
|
||||
worker::{exists_in_cache, get_annotation, save_cache, write_file},
|
||||
worker::{exists_in_cache, get_annotation_ts, save_cache, write_file},
|
||||
DB,
|
||||
};
|
||||
|
||||
@@ -112,6 +121,9 @@ 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 {
|
||||
@@ -245,6 +257,9 @@ 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 {
|
||||
@@ -453,6 +468,10 @@ 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,
|
||||
@@ -487,7 +506,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 OccupancyMetrics,
|
||||
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
|
||||
) -> Result<()> {
|
||||
let mut child = Command::new(&*BUN_PATH);
|
||||
child
|
||||
@@ -498,6 +517,10 @@ 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(
|
||||
@@ -512,7 +535,7 @@ pub async fn generate_bun_bundle(
|
||||
"bun build",
|
||||
timeout,
|
||||
false,
|
||||
&mut Some(occupancy_metrics),
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -540,7 +563,11 @@ 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()
|
||||
@@ -553,7 +580,11 @@ 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?;
|
||||
@@ -619,7 +650,7 @@ pub async fn prebundle_bun_script(
|
||||
base_internal_url: &str,
|
||||
worker_name: &str,
|
||||
token: &str,
|
||||
occupancy_metrics: &mut OccupancyMetrics,
|
||||
occupancy_metrics: &mut Option<&mut OccupancyMetrics>,
|
||||
) -> Result<()> {
|
||||
let (local_path, remote_path) = compute_bundle_local_and_remote_path(
|
||||
inner_content,
|
||||
@@ -632,7 +663,7 @@ pub async fn prebundle_bun_script(
|
||||
if exists_in_cache(&local_path, &remote_path).await {
|
||||
return Ok(());
|
||||
}
|
||||
let annotation = get_annotation(inner_content);
|
||||
let annotation = get_annotation_ts(inner_content);
|
||||
if annotation.nobundling {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -722,6 +753,10 @@ 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)
|
||||
}
|
||||
@@ -765,7 +800,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(inner_content);
|
||||
let mut annotation = windmill_common::worker::get_annotation_ts(inner_content);
|
||||
|
||||
let (mut has_bundle_cache, cache_logs, local_path, remote_path) =
|
||||
if requirements_o.is_some() && !annotation.nobundling && codebase.is_none() {
|
||||
@@ -815,10 +850,23 @@ pub async fn handle_bun_job(
|
||||
));
|
||||
}
|
||||
|
||||
let mut gbuntar_name = None;
|
||||
let mut gbuntar_name: Option<String> = None;
|
||||
if has_bundle_cache {
|
||||
let target = format!("{job_dir}/main.js");
|
||||
std::os::unix::fs::symlink(&local_path, &target).map_err(|e| {
|
||||
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| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"could not copy cached binary from {local_path} to {job_dir}/main: {e:?}"
|
||||
))
|
||||
@@ -1142,7 +1190,7 @@ try {{
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
&common_bun_proc_envs,
|
||||
occupancy_metrics,
|
||||
&mut Some(occupancy_metrics),
|
||||
)
|
||||
.await?;
|
||||
if !local_path.is_empty() {
|
||||
@@ -1190,51 +1238,58 @@ try {{
|
||||
}
|
||||
}
|
||||
if annotation.native_mode {
|
||||
let env_code = format!(
|
||||
#[cfg(not(feature = "deno_core"))]
|
||||
return Ok(to_raw_value("").unwrap());
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -1326,6 +1381,10 @@ 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");
|
||||
@@ -1352,8 +1411,13 @@ 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 {
|
||||
@@ -1461,7 +1525,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(inner_content);
|
||||
let mut annotation = windmill_common::worker::get_annotation_ts(inner_content);
|
||||
|
||||
//TODO: remove this when bun dedicated workers work without issues
|
||||
annotation.nodejs_mode = true;
|
||||
|
||||
@@ -225,7 +225,12 @@ func Run(req Req) (interface{{}}, error){{
|
||||
}
|
||||
} else {
|
||||
let target = format!("{job_dir}/main");
|
||||
std::os::unix::fs::symlink(&bin_path, &target).map_err(|e| {
|
||||
#[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| {
|
||||
Error::ExecutionErr(format!(
|
||||
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
|
||||
))
|
||||
|
||||
@@ -6,7 +6,11 @@ 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};
|
||||
@@ -49,6 +53,33 @@ 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)
|
||||
@@ -196,9 +227,26 @@ pub async fn handle_child(
|
||||
}
|
||||
}
|
||||
}
|
||||
/* send SIGKILL and reap child process */
|
||||
let (_, kill) = future::join(set_reason, child.kill()).await;
|
||||
kill.map(|()| Err(kill_reason))
|
||||
#[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))
|
||||
}
|
||||
};
|
||||
|
||||
/* a future that reads output from the child and appends to the database */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,56 +6,72 @@
|
||||
* 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;
|
||||
use windmill_common::{error::Error, flow_status::JobResult, DB};
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_common::error::Error;
|
||||
|
||||
use windmill_common::{flow_status::JobResult, DB};
|
||||
use windmill_queue::CanceledBy;
|
||||
|
||||
use crate::{
|
||||
common::{unsafe_raw, OccupancyMetrics},
|
||||
handle_child::run_future_with_polling_update_job_poller,
|
||||
AuthedClient,
|
||||
};
|
||||
use crate::{common::OccupancyMetrics, AuthedClient};
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use crate::{common::unsafe_raw, handle_child::run_future_with_polling_update_job_poller};
|
||||
|
||||
#[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 {
|
||||
@@ -73,14 +89,17 @@ 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(
|
||||
@@ -101,6 +120,7 @@ impl FetchPermissions for PermissionsContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
impl TimersPermission for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn allow_hrtime(&mut self) -> bool {
|
||||
@@ -108,6 +128,7 @@ impl TimersPermission for PermissionsContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
impl NetPermissions for PermissionsContainer {
|
||||
fn check_read(
|
||||
&mut self,
|
||||
@@ -134,6 +155,7 @@ impl NetPermissions for PermissionsContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
pub struct OptAuthedClient(Option<AuthedClient>);
|
||||
|
||||
pub async fn eval_timeout(
|
||||
@@ -142,7 +164,7 @@ pub async fn eval_timeout(
|
||||
flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
|
||||
authed_client: Option<&AuthedClient>,
|
||||
by_id: Option<IdContext>,
|
||||
ctx: Option<Vec<(String, String)>>,
|
||||
#[allow(unused_variables)] ctx: Option<Vec<(String, String)>>,
|
||||
) -> anyhow::Result<Box<RawValue>> {
|
||||
let expr = expr.trim().to_string();
|
||||
|
||||
@@ -212,121 +234,133 @@ pub async fn eval_timeout(
|
||||
}
|
||||
}
|
||||
|
||||
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(not(feature = "deno_core"))]
|
||||
{
|
||||
#[allow(unreachable_code)]
|
||||
return todo!();
|
||||
}
|
||||
|
||||
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(),
|
||||
])
|
||||
}
|
||||
#[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 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(),
|
||||
);
|
||||
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(),
|
||||
])
|
||||
}
|
||||
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()))?;
|
||||
if by_id.is_some() && authed_client.is_some() {
|
||||
ops.push(op_get_result());
|
||||
ops.push(op_get_id());
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
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()
|
||||
};
|
||||
|
||||
// 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 mut context_keys = transform_context
|
||||
.keys()
|
||||
.filter(|x| expr.contains(&x.to_string()))
|
||||
.map(|x| x.clone())
|
||||
.collect_vec();
|
||||
|
||||
let expr = replace_with_await_result(expr);
|
||||
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 r = runtime.block_on(eval(
|
||||
&mut js_runtime,
|
||||
&expr,
|
||||
context_keys,
|
||||
by_id,
|
||||
has_client,
|
||||
ctx,
|
||||
))?;
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
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)"
|
||||
))
|
||||
})??
|
||||
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);
|
||||
@@ -345,10 +379,12 @@ 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;
|
||||
@@ -368,6 +404,7 @@ fn add_closing_bracket(s: &str) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
async fn eval(
|
||||
context: &mut JsRuntime,
|
||||
expr: &str,
|
||||
@@ -508,6 +545,7 @@ 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(
|
||||
@@ -522,6 +560,7 @@ async fn op_variable(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[op2(async)]
|
||||
#[string]
|
||||
async fn op_get_result(
|
||||
@@ -540,6 +579,7 @@ async fn op_get_result(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[op2(async)]
|
||||
#[string]
|
||||
async fn op_get_id(
|
||||
@@ -563,6 +603,7 @@ async fn op_get_id(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[op2(async)]
|
||||
#[string]
|
||||
async fn op_resource(
|
||||
@@ -580,11 +621,13 @@ 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 {
|
||||
@@ -605,6 +648,7 @@ 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")?,
|
||||
@@ -621,21 +665,30 @@ 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 };
|
||||
|
||||
@@ -655,6 +708,7 @@ 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| {
|
||||
(
|
||||
@@ -675,7 +729,27 @@ 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,
|
||||
@@ -851,8 +925,10 @@ 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,
|
||||
@@ -898,6 +974,7 @@ 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>> {
|
||||
@@ -910,6 +987,7 @@ 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);
|
||||
@@ -920,6 +998,7 @@ fn op_log(op_state: Rc<RefCell<OpState>>, #[string] log: &str) {
|
||||
.push_str(log);
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -34,5 +34,7 @@ pub use worker::*;
|
||||
|
||||
pub use result_processor::handle_job_error;
|
||||
|
||||
pub use bun_executor::{get_common_bun_proc_envs, install_bun_lockfile, prepare_job_dir};
|
||||
pub use bun_executor::{
|
||||
get_common_bun_proc_envs, install_bun_lockfile, prebundle_bun_script, prepare_job_dir,
|
||||
};
|
||||
pub use deno_executor::generate_deno_lock;
|
||||
|
||||
@@ -29,6 +29,9 @@ 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();
|
||||
@@ -36,6 +39,9 @@ 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();
|
||||
|
||||
@@ -61,9 +67,12 @@ 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,
|
||||
PIP_INDEX_URL, TZ_ENV, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::SYSTEM_ROOT;
|
||||
|
||||
pub async fn create_dependencies_dir(job_dir: &str) {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
@@ -93,7 +102,7 @@ pub fn handle_ephemeral_token(x: String) -> String {
|
||||
x
|
||||
}
|
||||
|
||||
pub async fn pip_compile(
|
||||
pub async fn uv_pip_compile(
|
||||
job_id: &Uuid,
|
||||
requirements: &str,
|
||||
mem_peak: &mut i32,
|
||||
@@ -103,6 +112,10 @@ pub async fn 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..."));
|
||||
@@ -139,83 +152,184 @@ pub async fn pip_compile(
|
||||
#[cfg(feature = "enterprise")]
|
||||
let requirements = replace_pip_secret(db, w_id, &requirements, worker_name, job_id).await?;
|
||||
|
||||
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 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 file = "requirements.in";
|
||||
|
||||
write_file(job_dir, file, &requirements)?;
|
||||
|
||||
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);
|
||||
// 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,
|
||||
)
|
||||
.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:?}")))?;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -399,6 +513,9 @@ 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()
|
||||
@@ -475,6 +592,10 @@ 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?
|
||||
};
|
||||
|
||||
@@ -774,6 +895,7 @@ 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(
|
||||
@@ -788,7 +910,7 @@ async fn handle_python_deps(
|
||||
if requirements.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
pip_compile(
|
||||
uv_pip_compile(
|
||||
job_id,
|
||||
&requirements,
|
||||
mem_peak,
|
||||
@@ -798,6 +920,8 @@ async fn handle_python_deps(
|
||||
worker_name,
|
||||
w_id,
|
||||
occupancy_metrics,
|
||||
annotation.no_uv,
|
||||
annotation.no_cache,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -1016,7 +1140,12 @@ 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",
|
||||
@@ -1072,19 +1201,35 @@ pub async fn handle_python_reqs(
|
||||
|
||||
tracing::debug!("pip install command: {:?}", command_args);
|
||||
|
||||
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(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 child = handle_child(
|
||||
|
||||
@@ -23,12 +23,28 @@ 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 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()));
|
||||
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();
|
||||
}
|
||||
|
||||
const RUST_OBJECT_STORE_PREFIX: &str = "rustbin/";
|
||||
@@ -126,6 +142,14 @@ 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,
|
||||
@@ -176,6 +200,16 @@ 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,
|
||||
@@ -279,7 +313,13 @@ pub async fn handle_rust_job(
|
||||
|
||||
let cache_logs = if cache {
|
||||
let target = format!("{job_dir}/main");
|
||||
std::os::unix::fs::symlink(&bin_path, &target).map_err(|e| {
|
||||
|
||||
#[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| {
|
||||
Error::ExecutionErr(format!(
|
||||
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
|
||||
))
|
||||
@@ -360,6 +400,9 @@ 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(
|
||||
|
||||
@@ -237,6 +237,7 @@ 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");
|
||||
@@ -348,7 +349,6 @@ 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,6 +389,12 @@ 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
|
||||
|
||||
|
||||
@@ -561,6 +561,7 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
branch_chosen: None,
|
||||
approvers: vec![],
|
||||
failed_retries: vec![],
|
||||
skipped: false,
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
@@ -698,6 +699,20 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
}
|
||||
}
|
||||
if success || (flow_jobs.is_some() && (skip_loop_failures || skip_branch_failure)) {
|
||||
let is_skipped = if current_module.as_ref().is_some_and(|m| m.skip_if.is_some()) {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT job_kind = 'identity' FROM completed_job WHERE id = $1",
|
||||
job_id_for_status
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!("error during skip check: {e:#}"))
|
||||
})?
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
success = true;
|
||||
(
|
||||
true,
|
||||
@@ -709,6 +724,7 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
branch_chosen,
|
||||
approvers: vec![],
|
||||
failed_retries: old_status.retry.failed_jobs.clone(),
|
||||
skipped: is_skipped,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
@@ -2344,6 +2360,7 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
branch_chosen: None,
|
||||
approvers: vec![],
|
||||
failed_retries: vec![],
|
||||
skipped: false,
|
||||
}))
|
||||
.bind(flow_job.id)
|
||||
.execute(db)
|
||||
|
||||
@@ -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, to_raw_value, to_raw_value_owned, write_file};
|
||||
use windmill_common::worker::{get_annotation_ts, 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, pip_compile};
|
||||
use crate::python_executor::{create_dependencies_dir, handle_python_reqs, uv_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(&content);
|
||||
let anns = get_annotation_ts(&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(&content);
|
||||
let anns = get_annotation_ts(&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(&content);
|
||||
let anns = get_annotation_ts(&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> = pip_compile(
|
||||
let req: std::result::Result<String, Error> = uv_pip_compile(
|
||||
job_id,
|
||||
&reqs,
|
||||
mem_peak,
|
||||
@@ -1290,6 +1290,8 @@ async fn python_dep(
|
||||
worker_name,
|
||||
w_id,
|
||||
occupancy_metrics,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
// install the dependencies to pre-fill the cache
|
||||
@@ -1434,8 +1436,9 @@ async fn capture_dependency_job(
|
||||
.await
|
||||
}
|
||||
ScriptLang::Bun | ScriptLang::Bunnative => {
|
||||
let npm_mode = npm_mode
|
||||
.unwrap_or_else(|| windmill_common::worker::get_annotation(job_raw_code).npm_mode);
|
||||
let npm_mode = npm_mode.unwrap_or_else(|| {
|
||||
windmill_common::worker::get_annotation_ts(job_raw_code).npm_mode
|
||||
});
|
||||
if !raw_deps {
|
||||
let _ = write_file(job_dir, "main.ts", job_raw_code)?;
|
||||
}
|
||||
@@ -1472,7 +1475,7 @@ async fn capture_dependency_job(
|
||||
base_internal_url,
|
||||
worker_name,
|
||||
&token,
|
||||
occupancy_metrics,
|
||||
&mut Some(occupancy_metrics),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.402.3";
|
||||
export const VERSION = "v1.405.5";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+32
-15
@@ -183,10 +183,19 @@ export type InstanceSyncOptions = {
|
||||
yes?: boolean;
|
||||
};
|
||||
|
||||
export async function pickInstance(opts: InstanceSyncOptions, allowNew: boolean) {
|
||||
export async function pickInstance(
|
||||
opts: InstanceSyncOptions,
|
||||
allowNew: boolean
|
||||
) {
|
||||
const instances = await allInstances();
|
||||
if (opts.baseUrl && opts.token) {
|
||||
log.info("Using instance fully defined by --base-url and --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,
|
||||
@@ -327,6 +336,7 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
|
||||
includeSettings: true,
|
||||
includeUsers: true,
|
||||
includeKey: true,
|
||||
yes: opts.yes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -335,12 +345,14 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
|
||||
);
|
||||
|
||||
if (localWorkspacesToDelete.length > 0) {
|
||||
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,
|
||||
});
|
||||
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,
|
||||
}));
|
||||
|
||||
if (confirmDelete) {
|
||||
for (const workspace of localWorkspacesToDelete) {
|
||||
@@ -478,6 +490,7 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
|
||||
includeSettings: true,
|
||||
includeUsers: true,
|
||||
includeKey: true,
|
||||
yes: opts.yes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,12 +498,14 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
|
||||
(w) => !localWorkspaces.find((l) => l.workspaceId === w.id)
|
||||
);
|
||||
if (workspacesToDelete.length > 0) {
|
||||
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,
|
||||
});
|
||||
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,
|
||||
}));
|
||||
|
||||
if (confirmDelete) {
|
||||
for (const workspace of workspacesToDelete) {
|
||||
@@ -544,7 +559,9 @@ async function whoami(opts: {}) {
|
||||
log.info(colors.green.underline(`global whoami infos:`));
|
||||
log.info(JSON.stringify(whoamiInfo, null, 2));
|
||||
} catch (error) {
|
||||
log.error(colors.red(`Failed to retrieve whoami information: ${error.message}`));
|
||||
log.error(
|
||||
colors.red(`Failed to retrieve whoami information: ${error.message}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.402.3";
|
||||
export const VERSION = "1.405.5";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, Command, log, Table } from "./deps.ts";
|
||||
import { colors, Command, log, SEP, Table } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
|
||||
@@ -42,8 +42,7 @@ export async function pushSchedule(
|
||||
schedule: Schedule | ScheduleFile | undefined,
|
||||
localSchedule: ScheduleFile
|
||||
): Promise<void> {
|
||||
path = removeType(path, "schedule");
|
||||
|
||||
path = removeType(path, "schedule").replaceAll(SEP, "/");
|
||||
log.debug(`Processing local schedule ${path}`);
|
||||
|
||||
// deleting old app if it exists in raw mode
|
||||
|
||||
+3
-3
@@ -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?`,
|
||||
message: `Do you want to apply these ${changes.length} changes to your local files?`,
|
||||
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?`,
|
||||
message: `Do you want to apply these ${changes.length} changes to the remote?`,
|
||||
default: true,
|
||||
}))
|
||||
) {
|
||||
|
||||
@@ -17,6 +17,9 @@ 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
|
||||
|
||||
@@ -16,6 +16,8 @@ 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
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.402.3",
|
||||
"version": "1.405.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.402.3",
|
||||
"version": "1.405.5",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.402.3",
|
||||
"version": "1.405.5",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -177,6 +177,11 @@
|
||||
"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",
|
||||
@@ -358,6 +363,9 @@
|
||||
"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"
|
||||
],
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
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="left">
|
||||
<Drawer bind:this={drawer} {placement}>
|
||||
<DrawerContent title="Edit Default Scripts" on:close={drawer.closeDrawer}>
|
||||
<DefaultScriptsInner />
|
||||
</DrawerContent>
|
||||
@@ -19,8 +23,10 @@
|
||||
on:click={drawer?.openDrawer}
|
||||
startIcon={{ icon: SettingsIcon }}
|
||||
color="light"
|
||||
size="xs2"
|
||||
{size}
|
||||
btnClasses="!text-tertiary"
|
||||
variant="contained">defaults</Button
|
||||
variant="contained"
|
||||
>
|
||||
{noText ? '' : 'defaults'}
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
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'][] {
|
||||
@@ -31,24 +32,32 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Alert title="Global to workspace" type="info" class="mb-4">
|
||||
<Alert title="Global to workspace" type="info" class="mb-4" size={small ? 'xs' : 'sm'}>
|
||||
This setting is only available to admins and will affect all users in the workspace.
|
||||
</Alert>
|
||||
<div class="h-full w-full flex-col gap-2 flex">
|
||||
<div class="h-full w-full flex-col {small ? 'gap-0' : 'gap-2'} flex">
|
||||
{#each langs as lang, i (lang)}
|
||||
<div
|
||||
animate:flip={{ duration: 300 }}
|
||||
class="w-full p-2 rounded border border-seconadry grid grid-cols-3"
|
||||
><h3>{lang}</h3>
|
||||
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>
|
||||
<div>
|
||||
{#if i > 0}
|
||||
<button on:click={() => changePosition(i ?? 0, true)} class="text-lg mr-2">
|
||||
<button
|
||||
on:click={() => changePosition(i ?? 0, true)}
|
||||
class={small ? 'mr-2 text-secondary text-sm' : 'text-lg mr-2'}
|
||||
title="Move up"
|
||||
>
|
||||
↑</button
|
||||
>
|
||||
{/if}
|
||||
{#if i < langs.length - 1}
|
||||
<button on:click={() => changePosition(i ?? 0, false)} class="text-lg mr-2"
|
||||
>↓</button
|
||||
<button
|
||||
on:click={() => changePosition(i ?? 0, false)}
|
||||
class={small ? 'mr-2 text-secondary text-sm' : 'text-lg mr-2'}
|
||||
title="Move down">↓</button
|
||||
>
|
||||
{/if}</div
|
||||
>
|
||||
|
||||
@@ -498,7 +498,8 @@
|
||||
saveDraft: () => {},
|
||||
initialPath: '',
|
||||
flowInputsStore: writable<FlowInput>({}),
|
||||
customUi: {}
|
||||
customUi: {},
|
||||
insertButtonOpen: writable(false)
|
||||
})
|
||||
|
||||
$: updateFlow($flowStore)
|
||||
|
||||
@@ -399,6 +399,7 @@
|
||||
selectedTriggerStore.set(selectedTrigger)
|
||||
}
|
||||
|
||||
let insertButtonOpen = writable<boolean>(false)
|
||||
setContext<FlowEditorContext>('FlowEditorContext', {
|
||||
selectedId: selectedIdStore,
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
@@ -414,7 +415,8 @@
|
||||
saveDraft,
|
||||
initialPath,
|
||||
flowInputsStore: writable<FlowInput>({}),
|
||||
customUi
|
||||
customUi,
|
||||
insertButtonOpen
|
||||
})
|
||||
|
||||
async function loadSchedule() {
|
||||
@@ -467,20 +469,24 @@
|
||||
}
|
||||
break
|
||||
case 'ArrowDown': {
|
||||
let ids = generateIds()
|
||||
let idx = ids.indexOf($selectedIdStore)
|
||||
if (idx > -1 && idx < ids.length - 1) {
|
||||
$selectedIdStore = ids[idx + 1]
|
||||
event.preventDefault()
|
||||
if (!$insertButtonOpen) {
|
||||
let ids = generateIds()
|
||||
let idx = ids.indexOf($selectedIdStore)
|
||||
if (idx > -1 && idx < ids.length - 1) {
|
||||
$selectedIdStore = ids[idx + 1]
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
let ids = generateIds()
|
||||
let idx = ids.indexOf($selectedIdStore)
|
||||
if (idx > 0 && idx < ids.length) {
|
||||
$selectedIdStore = ids[idx - 1]
|
||||
event.preventDefault()
|
||||
if (!$insertButtonOpen) {
|
||||
let ids = generateIds()
|
||||
let idx = ids.indexOf($selectedIdStore)
|
||||
if (idx > 0 && idx < ids.length) {
|
||||
$selectedIdStore = ids[idx - 1]
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -567,6 +573,8 @@
|
||||
kind: string
|
||||
app: string
|
||||
ask_id: number
|
||||
id: number
|
||||
version_id: number
|
||||
}[]
|
||||
} catch (err) {
|
||||
if (err.name !== 'CancelError') throw err
|
||||
@@ -896,7 +904,8 @@
|
||||
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)) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
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
|
||||
@@ -40,7 +41,6 @@
|
||||
let logOffset = 0
|
||||
async function getLogs() {
|
||||
if (jobId) {
|
||||
|
||||
const getUpdate = await JobService.getJobUpdates({
|
||||
workspace: workspaceId ?? $workspaceStore!,
|
||||
id: jobId,
|
||||
@@ -85,6 +85,12 @@
|
||||
<div class="absolute z-40 text-xs top-0 left-1"
|
||||
><button class="" on:click={drawer.openDrawer}>explore all steps' logs</button></div
|
||||
>
|
||||
<LogViewer content={logs ?? ''} {jobId} isLoading={waitingForExecutor} {tag} />
|
||||
<LogViewer
|
||||
download={downloadLogs}
|
||||
content={logs ?? ''}
|
||||
{jobId}
|
||||
isLoading={waitingForExecutor}
|
||||
{tag}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let hideDownloadInGraph = false
|
||||
export let hideNodeDefinition = false
|
||||
export let hideJobId = false
|
||||
export let hideDownloadLogs = false
|
||||
|
||||
export let isOwner = false
|
||||
export let wideResults = false
|
||||
@@ -33,7 +34,8 @@
|
||||
hideDownloadInGraph,
|
||||
hideNodeDefinition,
|
||||
hideTimeline,
|
||||
hideJobId
|
||||
hideJobId,
|
||||
hideDownloadLogs
|
||||
})
|
||||
|
||||
function loadOwner(path: string) {
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
suspendStatus,
|
||||
hideDownloadInGraph,
|
||||
hideTimeline,
|
||||
hideNodeDefinition
|
||||
hideNodeDefinition,
|
||||
hideDownloadLogs
|
||||
} = getContext<FlowStatusViewerContext>('FlowStatusViewer')
|
||||
|
||||
export let jobId: string
|
||||
@@ -437,7 +438,8 @@
|
||||
flow_jobs: mod.flow_jobs,
|
||||
flow_jobs_success: mod.flow_jobs_success,
|
||||
iteration_total: mod.iterator?.itered?.length,
|
||||
retries: mod?.failed_retries?.length
|
||||
retries: mod?.failed_retries?.length,
|
||||
skipped: mod.skipped
|
||||
// retries: $flowStateStore?.raw_flow
|
||||
},
|
||||
force
|
||||
@@ -668,6 +670,7 @@
|
||||
result={job.result}
|
||||
logs={job.logs}
|
||||
durationStates={localDurationStatuses}
|
||||
downloadLogs={!hideDownloadLogs}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1025,6 +1028,7 @@
|
||||
result={job['result']}
|
||||
logs={job.logs ?? ''}
|
||||
durationStates={localDurationStatuses}
|
||||
downloadLogs={!hideDownloadLogs}
|
||||
/>
|
||||
{:else if selectedNode == 'start'}
|
||||
{#if job.args}
|
||||
@@ -1054,7 +1058,11 @@
|
||||
<span class="pl-1 text-tertiary text-lg pt-4">Selected subflow</span>
|
||||
{/if}
|
||||
<div class="px-2 flex gap-2 min-w-0 w-full">
|
||||
<ModuleStatus type={node.type} scheduled_for={node.scheduled_for} />
|
||||
<ModuleStatus
|
||||
type={node.type}
|
||||
scheduled_for={node.scheduled_for}
|
||||
skipped={node.skipped}
|
||||
/>
|
||||
{#if node.duration_ms}
|
||||
<Badge>
|
||||
<Hourglass class="mr-2" size={10} />
|
||||
@@ -1096,6 +1104,7 @@
|
||||
tag={node.tag}
|
||||
logs={node.logs}
|
||||
durationStates={localDurationStatuses}
|
||||
downloadLogs={!hideDownloadLogs}
|
||||
/>
|
||||
{:else}
|
||||
<p class="p-2 text-tertiary italic"
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
bind:this={inputDiv}
|
||||
autofocus
|
||||
type="text"
|
||||
bind:value
|
||||
class="!w-auto grow"
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
export let drawerOpen = false
|
||||
export let noMaxH = false
|
||||
export let noAutoScroll = false
|
||||
export let download = true
|
||||
|
||||
// @ts-ignore
|
||||
const ansi_up = new AnsiUp()
|
||||
@@ -93,7 +94,7 @@
|
||||
loadedFromObjectStore: string,
|
||||
limit: number
|
||||
) {
|
||||
let content = loadedFromObjectStore + jobContent ?? ''
|
||||
let content = loadedFromObjectStore + (jobContent ?? '')
|
||||
if (content.length > limit) {
|
||||
return content.substring(content.length - limit)
|
||||
}
|
||||
@@ -148,7 +149,7 @@
|
||||
<Drawer bind:this={logViewer} bind:open={drawerOpen} size="900px">
|
||||
<DrawerContent title="Expanded Logs" on:close={logViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if jobId}
|
||||
{#if jobId && download}
|
||||
<Button
|
||||
href="{base}/api/w/{$workspaceStore}/jobs_u/get_logs/{jobId}"
|
||||
download="windmill_logs_{jobId}.txt"
|
||||
@@ -196,7 +197,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}
|
||||
{#if jobId && download}
|
||||
<div class="flex items-center">
|
||||
<a
|
||||
class="text-primary pb-0.5"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
export let type: FlowStatusModule['type']
|
||||
export let scheduled_for: Date | undefined
|
||||
export let skipped: boolean = false
|
||||
</script>
|
||||
|
||||
{#if type == 'WaitingForEvents'}
|
||||
@@ -28,6 +29,8 @@
|
||||
Job is waiting for an executor
|
||||
{/if}
|
||||
</span>
|
||||
{:else if skipped}
|
||||
<Badge color="blue">Skipped</Badge>
|
||||
{:else if type == 'Success'}
|
||||
<Badge color="green">Success</Badge>
|
||||
{:else if type == 'Failure'}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<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>
|
||||
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
@@ -258,7 +258,7 @@
|
||||
<IdEditor
|
||||
{id}
|
||||
on:selected={() => ($selectedComponent = [id])}
|
||||
on:change={({ detail }) => renameId(detail)}
|
||||
on:save={({ detail }) => renameId(detail)}
|
||||
/></div
|
||||
>
|
||||
{/if}
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
'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-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'
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
}
|
||||
|
||||
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">
|
||||
@@ -24,22 +25,30 @@
|
||||
</div>
|
||||
</PopoverButton>
|
||||
<ConditionalPortal condition={shouldUsePortal} {target}>
|
||||
<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 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 class={containerClasses}>
|
||||
<slot {close} />
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Transition>
|
||||
{/if}
|
||||
</div>
|
||||
</ConditionalPortal>
|
||||
</Popover>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<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>
|
||||
@@ -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 }}
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<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>
|
||||
@@ -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,6 +25,8 @@ export type FlowCopilotModule = {
|
||||
kind: string
|
||||
app: string
|
||||
ask_id: number
|
||||
id: number
|
||||
version_id: number
|
||||
}[]
|
||||
selectedCompletion:
|
||||
| {
|
||||
@@ -104,6 +106,69 @@ 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.
|
||||
|
||||
@@ -200,6 +265,8 @@ export async function stepCopilot(
|
||||
let prompt =
|
||||
module.type === 'trigger'
|
||||
? triggerPrompts[lang]
|
||||
// : module.type === 'preprocessor'
|
||||
// ? preprocessorPrompts[lang]
|
||||
: pastModule === undefined
|
||||
? firstActionPrompt
|
||||
: isFirstInLoop
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex flex-row items-center">
|
||||
App <LayoutDashboard class="ml-1.5" size={18} />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
export let smallErrorHandler = false
|
||||
export let newFlow: boolean = false
|
||||
|
||||
let size = 40
|
||||
let size = 50
|
||||
|
||||
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' : ''
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<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>
|
||||
@@ -0,0 +1,466 @@
|
||||
<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>
|
||||
@@ -0,0 +1,33 @@
|
||||
<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">↵</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -3,37 +3,52 @@
|
||||
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 } from 'lucide-svelte'
|
||||
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'
|
||||
|
||||
export let small: boolean
|
||||
|
||||
const { selectedId, flowStateStore, flowStore } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$flowStore.value.failure_module = {
|
||||
id: 'failure',
|
||||
value: { type: 'identity' }
|
||||
}
|
||||
$flowStateStore['failure'] = failureModule
|
||||
|
||||
$selectedId = 'failure'
|
||||
$flowStore = $flowStore
|
||||
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
|
||||
}
|
||||
|
||||
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 = module
|
||||
$flowStateStore[module.id] = state
|
||||
|
||||
$selectedId = 'failure'
|
||||
$flowStore = $flowStore
|
||||
}
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
@@ -42,45 +57,71 @@
|
||||
|
||||
<!-- 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 justify-between items-center flex-wrap gap-2">
|
||||
<Bug size={16} />
|
||||
<span class="font-bold text-xs">Error Handler</span>
|
||||
<div class="flex items-center grow-0 min-w-0 gap-2">
|
||||
<Bug size={16} color={$flowStore?.value?.failure_module ? '#3b82f6' : '#9CA3AF'} />
|
||||
</div>
|
||||
|
||||
<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"
|
||||
/>
|
||||
{#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>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
PhoneIncoming,
|
||||
Repeat,
|
||||
Square,
|
||||
SkipForward,
|
||||
Voicemail,
|
||||
X
|
||||
} from 'lucide-svelte'
|
||||
@@ -33,6 +34,7 @@
|
||||
export let retry: boolean = false
|
||||
export let cache: boolean = false
|
||||
export let earlyStop: boolean = false
|
||||
export let skip: boolean = false
|
||||
export let suspend: boolean = false
|
||||
export let sleep: boolean = false
|
||||
export let mock: boolean = false
|
||||
@@ -181,6 +183,17 @@
|
||||
<svelte:fragment slot="text">Early stop/break</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if skip}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
|
||||
>
|
||||
<SkipForward size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Skip</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if suspend}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { createEventDispatcher, getContext, tick } from 'svelte'
|
||||
import {
|
||||
createInlineScriptModule,
|
||||
createBranchAll,
|
||||
createBranches,
|
||||
createLoop,
|
||||
createWhileLoop,
|
||||
deleteFlowStateById,
|
||||
emptyModule,
|
||||
pickScript
|
||||
pickScript,
|
||||
pickFlow
|
||||
} from '$lib/components/flows/flowStateUtils'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { FlowModule, RawScript, Script } from '$lib/gen'
|
||||
import { emptyFlowModuleState, initFlowStepWarnings } from '../utils'
|
||||
import FlowSettingsItem from './FlowSettingsItem.svelte'
|
||||
import FlowConstantsItem from './FlowConstantsItem.svelte'
|
||||
@@ -31,8 +33,6 @@
|
||||
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,12 +61,22 @@
|
||||
| 'trigger'
|
||||
| 'approval'
|
||||
| 'end',
|
||||
wsScript?: { path: string; summary: string; hash: string | undefined }
|
||||
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
|
||||
}
|
||||
): Promise<FlowModule[]> {
|
||||
push(history, $flowStore)
|
||||
var module = emptyModule($flowStateStore, $flowStore, kind == 'flow')
|
||||
var state = emptyFlowModuleState()
|
||||
if (wsScript) {
|
||||
if (wsFlow) {
|
||||
;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id)
|
||||
} else if (wsScript) {
|
||||
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
|
||||
} else if (kind == 'forloop') {
|
||||
;[module, state] = await createLoop(
|
||||
@@ -89,11 +99,50 @@
|
||||
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
|
||||
)
|
||||
$flowStateStore[module.id] = state
|
||||
}
|
||||
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) {
|
||||
@@ -314,22 +363,16 @@
|
||||
$moving = undefined
|
||||
} else {
|
||||
if (detail.detail === 'preprocessor') {
|
||||
const preprocessorModule = {
|
||||
schema: emptySchema(),
|
||||
previewResult: NEVER_TESTED_THIS_FAR
|
||||
}
|
||||
$flowStore.value.preprocessor_module = {
|
||||
id: 'preprocessor',
|
||||
value: { type: 'identity' }
|
||||
}
|
||||
$flowStateStore['preprocessor'] = preprocessorModule
|
||||
insertNewPreprocessorModule(detail.inlineScript, detail.script)
|
||||
$selectedId = 'preprocessor'
|
||||
} else {
|
||||
await insertNewModuleAtIndex(
|
||||
detail.modules,
|
||||
detail.index ?? 0,
|
||||
detail.detail,
|
||||
detail.script
|
||||
detail.kind,
|
||||
detail.script,
|
||||
detail.flow,
|
||||
detail.inlineScript
|
||||
)
|
||||
$selectedId = detail.modules[detail.index ?? 0].id
|
||||
}
|
||||
@@ -338,7 +381,7 @@
|
||||
if (['branchone', 'branchall'].includes(detail.detail)) {
|
||||
await addBranch(detail.modules[detail.index ?? 0])
|
||||
}
|
||||
|
||||
$flowStateStore = $flowStateStore
|
||||
$flowStore = $flowStore
|
||||
dispatch('change')
|
||||
}
|
||||
|
||||
@@ -1,169 +1,200 @@
|
||||
<script lang="ts">
|
||||
import { Menu } from '$lib/components/common'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { CheckCircle2, Code, Cross, GitBranch, Repeat, Square, Zap } from 'lucide-svelte'
|
||||
import StepGen from '$lib/components/copilot/StepGen.svelte'
|
||||
import { Cross, Zap } from 'lucide-svelte'
|
||||
import StepGenQuick from '$lib/components/copilot/StepGenQuick.svelte'
|
||||
import FlowInputsQuick from '../content/FlowInputsQuick.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 open: boolean | undefined = undefined
|
||||
export let index: number
|
||||
export let index: number = 0
|
||||
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="bottom-center"
|
||||
let:close
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
<!-- <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">
|
||||
<button
|
||||
title="Add step"
|
||||
title={`Add ${
|
||||
kind === 'failure'
|
||||
? ' failure module '
|
||||
: kind === 'preprocessor'
|
||||
? 'preprocessor step'
|
||||
: kind === 'trigger'
|
||||
? 'trigger'
|
||||
: '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}
|
||||
>
|
||||
<Cross size={12} />
|
||||
{#if kind === 'trigger'}
|
||||
<Zap size={12} />
|
||||
{:else}
|
||||
<Cross size={12} />
|
||||
{/if}
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<div id="flow-editor-insert-module">
|
||||
<StepGen on:insert {index} bind:funcDesc bind:open {close} {modules} {disableAi} />
|
||||
<!-- 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>
|
||||
|
||||
{#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')
|
||||
<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'
|
||||
}}
|
||||
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} />
|
||||
|
||||
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} />
|
||||
|
||||
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')
|
||||
/>
|
||||
{#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'
|
||||
}}
|
||||
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')
|
||||
/>
|
||||
{#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}
|
||||
|
||||
<TopLevelNode
|
||||
label="For Loop"
|
||||
on:select={() => {
|
||||
close(null)
|
||||
dispatch('new', { kind: 'forloop' })
|
||||
}}
|
||||
role="menuitem"
|
||||
>
|
||||
<Square size={14} />
|
||||
End Flow
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
/>
|
||||
<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}
|
||||
|
||||
<FlowInputsQuick
|
||||
{selectedKind}
|
||||
bind:loading
|
||||
filter={funcDesc}
|
||||
{modules}
|
||||
{index}
|
||||
{disableAi}
|
||||
{funcDesc}
|
||||
{kind}
|
||||
on:close={() => {
|
||||
close(null)
|
||||
}}
|
||||
on:new
|
||||
on:pickScript
|
||||
on:pickFlow
|
||||
{preFilter}
|
||||
{small}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
</PopupV2>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Menu } from '$lib/components/common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import StepGen from '$lib/components/copilot/StepGen.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { Zap } from 'lucide-svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
export let open: boolean | undefined = undefined
|
||||
export let index: number
|
||||
export let funcDesc = ''
|
||||
export let modules: FlowModule[]
|
||||
export let disableAi = false
|
||||
|
||||
$: !open && (funcDesc = '')
|
||||
</script>
|
||||
|
||||
<Menu
|
||||
transitionDuration={0}
|
||||
pointerDown
|
||||
bind:show={open}
|
||||
noMinW
|
||||
placement="bottom-center"
|
||||
let:close
|
||||
>
|
||||
<button
|
||||
title="Add a Trigger"
|
||||
slot="trigger"
|
||||
type="button"
|
||||
class="text-secondary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 rotate-180 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded text-sm w-[20px] h-[20px] flex items-center justify-center"
|
||||
>
|
||||
<Zap size={12} />
|
||||
</button>
|
||||
{#if !disableAi}
|
||||
<StepGen {index} bind:funcDesc bind:open {close} {modules} trigger on:insert />
|
||||
{/if}
|
||||
{#if funcDesc.length === 0}
|
||||
<div class="font-mono divide-y text-xs w-full text-secondary whitespace-nowrap">
|
||||
<button
|
||||
class="w-full text-left py-2 px-3 hover:bg-surface-hover flex flex-row items-center gap-2"
|
||||
on:pointerdown={() => {
|
||||
close()
|
||||
dispatch('new', 'trigger')
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Zap size={14} />Trigger
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</Menu>
|
||||
@@ -48,6 +48,7 @@
|
||||
selected: $selectedId === mod.id,
|
||||
retry: mod.retry?.constant != undefined || mod.retry?.exponential != undefined,
|
||||
earlyStop: mod.stop_after_if != undefined || mod.stop_after_all_iters_if != undefined,
|
||||
skip: Boolean(mod.skip_if),
|
||||
suspend: Boolean(mod.suspend),
|
||||
sleep: Boolean(mod.sleep),
|
||||
cache: Boolean(mod.cache_ttl),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let label: string
|
||||
export let lang: SupportedLanguage | 'docker' | 'javascript' | undefined = undefined
|
||||
export let selected = false
|
||||
export let eeRestricted: boolean
|
||||
export let enterpriseLangs: string[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
function handleKeydown(event: KeyboardEvent & { currentTarget: EventTarget & Window }) {
|
||||
if (selected && event.key === 'Enter') {
|
||||
click()
|
||||
}
|
||||
}
|
||||
|
||||
function click() {
|
||||
if (eeRestricted) {
|
||||
sendUserToast(
|
||||
`The languages ${enterpriseLangs.join(', ')} are only available on the enterprise edition`,
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
dispatch('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
<button
|
||||
class={twMerge(
|
||||
'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={click}
|
||||
role="menuitem"
|
||||
>
|
||||
{#if lang}
|
||||
<LanguageIcon {lang} width={14} height={14} />
|
||||
{/if}
|
||||
<span
|
||||
class="grow truncate text-left text-2xs font-normal {eeRestricted
|
||||
? 'text-secondary'
|
||||
: 'text-primary'}"
|
||||
>
|
||||
{label}{#if eeRestricted} (EE){/if}
|
||||
</span>
|
||||
{#if selected}
|
||||
<kbd class="!text-xs">↵</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import TopLevelNode from './TopLevelNode.svelte'
|
||||
|
||||
export let label: string
|
||||
export let selected = false
|
||||
const dispatch = createEventDispatcher()
|
||||
function handleKeydown(event: KeyboardEvent & { currentTarget: EventTarget & Window }) {
|
||||
if (selected && event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
click()
|
||||
}
|
||||
}
|
||||
|
||||
function click() {
|
||||
dispatch('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
<TopLevelNode class="px-3" {label} {selected} returnIcon on:select={click} />
|
||||
@@ -0,0 +1,175 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { APP_TO_ICON_COMPONENT } from '$lib/components/icons'
|
||||
import { IntegrationService, ScriptService, type HubScriptKind } from '$lib/gen'
|
||||
import { Circle } from 'lucide-svelte'
|
||||
|
||||
export let kind: HubScriptKind & string = 'script'
|
||||
export let filter = ''
|
||||
|
||||
export let loading = false
|
||||
export let selected: number | undefined = undefined
|
||||
let hubNotAvailable = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let appFilter: string | undefined = undefined
|
||||
export let items: {
|
||||
path: string
|
||||
summary: string
|
||||
id: number
|
||||
version_id: number
|
||||
ask_id: number
|
||||
app: string
|
||||
kind: HubScriptKind
|
||||
}[] = []
|
||||
|
||||
export let apps: string[] = []
|
||||
let allApps: string[] = []
|
||||
$: applyFilter(filter, kind, appFilter)
|
||||
$: getAllApps(kind)
|
||||
|
||||
async function getAllApps(filterKind: typeof kind) {
|
||||
try {
|
||||
hubNotAvailable = false
|
||||
allApps = (
|
||||
await IntegrationService.listHubIntegrations({
|
||||
kind: filterKind
|
||||
})
|
||||
).map((x) => x.name)
|
||||
apps = allApps
|
||||
} catch (err) {
|
||||
console.error('Hub is not available')
|
||||
allApps = []
|
||||
apps = []
|
||||
hubNotAvailable = true
|
||||
}
|
||||
}
|
||||
|
||||
let startTs = 0
|
||||
async function applyFilter(
|
||||
filter: string,
|
||||
filterKind: typeof kind,
|
||||
appFilter: string | undefined
|
||||
) {
|
||||
try {
|
||||
loading = true
|
||||
hubNotAvailable = false
|
||||
const ts = Date.now()
|
||||
startTs = ts
|
||||
await new Promise((resolved, rejected) => setTimeout(resolved, 200))
|
||||
if (ts < startTs) return
|
||||
const scripts =
|
||||
filter.length > 0
|
||||
? await ScriptService.queryHubScripts({
|
||||
text: `${filter}`,
|
||||
limit: 40,
|
||||
kind: filterKind
|
||||
})
|
||||
: (
|
||||
await ScriptService.getTopHubScripts({
|
||||
limit: 40,
|
||||
kind: filterKind,
|
||||
app: appFilter
|
||||
})
|
||||
).asks ?? []
|
||||
|
||||
const mappedItems = scripts.map(
|
||||
(x: {
|
||||
summary: string
|
||||
version_id: number
|
||||
id: number
|
||||
ask_id: number
|
||||
app: string
|
||||
kind: HubScriptKind
|
||||
}) => ({
|
||||
...x,
|
||||
path: `hub/${x.version_id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
|
||||
summary: `${x.summary} (${x.app})`
|
||||
})
|
||||
)
|
||||
if (filter.length > 0) {
|
||||
apps = Array.from(new Set(mappedItems?.map((x) => x.app) ?? [])).sort()
|
||||
} else {
|
||||
apps = allApps
|
||||
}
|
||||
|
||||
items = appFilter ? mappedItems.filter((x) => x.app === appFilter) : mappedItems
|
||||
|
||||
if (ts === startTs) {
|
||||
loading = false
|
||||
}
|
||||
|
||||
hubNotAvailable = false
|
||||
} catch (err) {
|
||||
hubNotAvailable = true
|
||||
console.error('Hub not available')
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (
|
||||
selected != undefined &&
|
||||
items &&
|
||||
selected >= 0 &&
|
||||
selected < items?.length! &&
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
e.preventDefault()
|
||||
let item = items![selected]
|
||||
dispatch('pickScript', item)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
{#if hubNotAvailable}
|
||||
<div class="text-2xs text-red-400 ftext-2xs font-light text-center py-2 px-3 items-center">
|
||||
Hub not available
|
||||
</div>
|
||||
{:else if loading}
|
||||
{#each Array(15).fill(0) as _}
|
||||
<Skeleton layout={[0.1, [1.5]]} />
|
||||
{/each}
|
||||
{:else if items.length > 0 && apps.length > 0}
|
||||
<ul>
|
||||
{#each items as item, index (item.path)}
|
||||
<li class="w-full">
|
||||
<button
|
||||
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover transition-all items-center rounded-md {index ===
|
||||
selected
|
||||
? 'bg-surface-hover'
|
||||
: ''}"
|
||||
on:click={() => dispatch('pickScript', item)}
|
||||
>
|
||||
<div class={classNames('flex justify-center items-center')}>
|
||||
{#if item['app'] in APP_TO_ICON_COMPONENT}
|
||||
<svelte:component this={APP_TO_ICON_COMPONENT[item['app']]} height={14} width={14} />
|
||||
{:else}
|
||||
<div
|
||||
class="w-[14px] h-[14px] text-gray-400 flex flex-row items-center justify-center"
|
||||
>
|
||||
<Circle size="12" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<span class="grow truncate text-left text-2xs text-primary font-normal">
|
||||
{item.summary ?? ''}
|
||||
</span>
|
||||
{#if index === selected}
|
||||
<kbd class="!text-xs">↵</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if items.length == 40}
|
||||
<div class="text-2xs text-tercary font-extralight text-center py-2 px-3 items-center">
|
||||
There are more items than being displayed. Refine your search.
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
||||
import { CheckCircle2, ChevronRight, Code, GitBranch, Repeat, Square, Zap } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let label: string
|
||||
export let selected = false
|
||||
export let returnIcon = false
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={twMerge(
|
||||
'w-full text-left py-2 px-1.5 hover:bg-surface-hover text-xs font-medium transition-all whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
|
||||
selected ? 'bg-surface-hover' : '',
|
||||
$$props.class
|
||||
)}
|
||||
on:pointerdown={() => dispatch('select', label)}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="grow flex items-center gap-2">
|
||||
{#if label === 'Action'}
|
||||
<Code size={14} />
|
||||
Action
|
||||
<ChevronRight size={12} class="ml-auto" color="#4c566a" />
|
||||
{:else if label === 'Trigger'}
|
||||
<Zap size={14} />
|
||||
Trigger
|
||||
<ChevronRight size={12} class="ml-auto" color="#4c566a" />
|
||||
{:else if label === 'Approval/Prompt'}
|
||||
<CheckCircle2 size={14} />
|
||||
Approval/Prompt
|
||||
<ChevronRight size={12} class="ml-auto" color="#4c566a" />
|
||||
{:else if label === 'Flow'}
|
||||
<BarsStaggered size={14} />
|
||||
Flow
|
||||
<ChevronRight size={12} class="ml-auto" color="#4c566a" />
|
||||
{:else if label === 'End Flow'}
|
||||
<Square size={14} />
|
||||
End Flow
|
||||
{:else if label === 'For Loop'}
|
||||
<Repeat size={14} />
|
||||
For Loop
|
||||
{:else if label === 'While Loop'}
|
||||
<Repeat size={14} />
|
||||
While Loop
|
||||
{:else if label === 'Branch to one'}
|
||||
<GitBranch size={14} />
|
||||
Branch to one
|
||||
{:else if label === 'Branch to all'}
|
||||
<GitBranch size={14} />
|
||||
Branch to all
|
||||
{/if}
|
||||
</span>
|
||||
{#if returnIcon && selected}
|
||||
<kbd class="!text-xs text-right">↵</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import { emptyString } from '$lib/utils'
|
||||
import { Code2 } from 'lucide-svelte'
|
||||
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
||||
|
||||
export let kind: 'script' | 'trigger' | 'approval' | 'failure' | 'flow' | 'preprocessor' =
|
||||
'script'
|
||||
export let isTemplate: boolean | undefined = undefined
|
||||
export let selected: number | undefined = undefined
|
||||
|
||||
type Item = {
|
||||
path: string
|
||||
summary?: string
|
||||
description?: string
|
||||
hash?: string
|
||||
}
|
||||
|
||||
let items: Item[] | undefined = undefined
|
||||
|
||||
let filteredItems: (Item & { marked?: string })[] | undefined = undefined
|
||||
export let filteredWithOwner: (Item & { marked?: string })[] | undefined = undefined
|
||||
|
||||
export let filter = ''
|
||||
export let owners: string[] = []
|
||||
|
||||
$: $workspaceStore && kind && loadItems()
|
||||
|
||||
async function loadItems(): Promise<void> {
|
||||
items =
|
||||
kind == 'flow'
|
||||
? await FlowService.listFlows({ workspace: $workspaceStore! })
|
||||
: await ScriptService.listScripts({
|
||||
workspace: $workspaceStore!,
|
||||
kinds: kind,
|
||||
isTemplate
|
||||
})
|
||||
}
|
||||
|
||||
export let ownerFilter:
|
||||
| { kind: 'inline' | 'owner' | 'integrations'; name: string | undefined }
|
||||
| undefined = undefined
|
||||
|
||||
$: if ($workspaceStore) {
|
||||
ownerFilter = undefined
|
||||
}
|
||||
|
||||
$: owners = Array.from(
|
||||
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
|
||||
).sort((a, b) => {
|
||||
if (a.startsWith('u/') && !b.startsWith('u/')) return -1
|
||||
if (b.startsWith('u/') && !a.startsWith('u/')) return 1
|
||||
|
||||
if (a.startsWith('f/') && !b.startsWith('f/')) return -1
|
||||
if (b.startsWith('f/') && !a.startsWith('f/')) return 1
|
||||
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let lockHash = false
|
||||
|
||||
$: filteredWithOwner =
|
||||
ownerFilter != undefined
|
||||
? filteredItems?.filter((x) => x.path.startsWith(ownerFilter?.name!))
|
||||
: filteredItems
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (
|
||||
selected != undefined &&
|
||||
filteredWithOwner &&
|
||||
selected >= 0 &&
|
||||
selected < filteredWithOwner.length &&
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
e.preventDefault()
|
||||
let item = filteredWithOwner[selected]
|
||||
if (kind == 'flow') {
|
||||
dispatch('pickFlow', { path: item.path })
|
||||
} else {
|
||||
dispatch('pickScript', { path: item.path, hash: lockHash ? item.hash : undefined })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SearchItems
|
||||
{filter}
|
||||
{items}
|
||||
bind:filteredItems
|
||||
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
|
||||
/>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
{#if filteredItems}
|
||||
{#if filteredItems.length == 0}
|
||||
<div class="text-2xs text-tertiary font-light text-center py-2 px-3 items-center">
|
||||
{kind == 'flow' ? 'No flows found.' : 'No scripts found.'}
|
||||
</div>
|
||||
{/if}
|
||||
<ul>
|
||||
{#each filteredWithOwner ?? [] as { path, hash, summary, marked }, index}
|
||||
<li class="w-full">
|
||||
<button
|
||||
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover transition-all items-center rounded-md {index ===
|
||||
selected
|
||||
? 'bg-surface-hover'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if (kind == 'flow') {
|
||||
dispatch('pickFlow', { path: path })
|
||||
} else {
|
||||
dispatch('pickScript', { path: path, hash: lockHash ? hash : undefined })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if kind == 'flow'}
|
||||
<BarsStaggered size={14} class="shrink-0" />
|
||||
{:else}
|
||||
<Code2 size={14} />
|
||||
{/if}
|
||||
<span class="grow min-w-0 truncate text-left text-2xs text-primary font-normal">
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{!summary || summary.length == 0 ? path : summary}
|
||||
{/if}</span
|
||||
>
|
||||
{#if index === selected}
|
||||
<kbd class="!text-xs">↵</kbd>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
{#each Array(10).fill(0) as _}
|
||||
<Skeleton layout={[0.5, [1.5]]} />
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -42,4 +42,5 @@ export type FlowEditorContext = {
|
||||
initialPath: string
|
||||
flowInputsStore: Writable<FlowInput>
|
||||
customUi: FlowBuilderWhitelabelCustomUi
|
||||
insertButtonOpen: Writable<boolean>
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
export let newFlow: boolean = false
|
||||
|
||||
export let insertable = false
|
||||
export let moving: string | undefined = undefined
|
||||
export let scroll = false
|
||||
export let moving: string | undefined = undefined
|
||||
|
||||
// Download: display a top level button to open the graph in a new tab
|
||||
export let download = false
|
||||
@@ -287,6 +287,9 @@
|
||||
</div>
|
||||
{:else}
|
||||
<SvelteFlow
|
||||
on:paneclick={(e) => {
|
||||
window.dispatchEvent(new Event('focus'))
|
||||
}}
|
||||
{nodes}
|
||||
{edges}
|
||||
{edgeTypes}
|
||||
|
||||
@@ -592,4 +592,5 @@ export default function graphBuilder(
|
||||
error: e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export type FlowStatusViewerContext = {
|
||||
hideTimeline?: boolean
|
||||
hideNodeDefinition?: boolean
|
||||
hideJobId?: boolean
|
||||
hideDownloadLogs
|
||||
}
|
||||
|
||||
export type GraphModuleState = {
|
||||
@@ -60,6 +61,7 @@ export type GraphModuleState = {
|
||||
started_at?: number
|
||||
suspend_count?: number
|
||||
isListJob?: boolean
|
||||
skipped?: boolean
|
||||
}
|
||||
|
||||
export type NestedNodes = GraphItem[]
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
import { getStraightLinePath } from '../utils'
|
||||
import InsertTriggerButton from '$lib/components/flows/map/InsertTriggerButton.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let sourceX: number
|
||||
@@ -47,33 +46,44 @@
|
||||
const { useDataflow } = getContext<{
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
}>('FlowGraphContext')
|
||||
|
||||
let menuOpen = false
|
||||
</script>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
{#if data?.insertable && !$useDataflow && !data?.moving}
|
||||
<div
|
||||
class={twMerge('edgeButtonContainer nodrag nopan top-0', menuOpen ? 'z-50' : '')}
|
||||
class={twMerge('edgeButtonContainer nodrag nopan top-0')}
|
||||
style:transform="translate(-50%, 50%) translate({sourceX}px,{sourceY + 2}px)"
|
||||
>
|
||||
<InsertModuleButton
|
||||
disableAi={data.disableAi}
|
||||
index={data.index ?? 0}
|
||||
trigger={data.enableTrigger}
|
||||
allowTrigger={data.enableTrigger}
|
||||
modules={data?.modules ?? []}
|
||||
on:new={(e) => {
|
||||
data?.eventHandlers.insert({ modules: data.modules, index: data.index, detail: e.detail })
|
||||
}}
|
||||
on:insert={(e) => {
|
||||
// console.log('new', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
script: e.detail,
|
||||
detail: 'script'
|
||||
kind: e.detail.kind,
|
||||
inlineScript: e.detail.inlineScript
|
||||
})
|
||||
}}
|
||||
on:pickScript={(e) => {
|
||||
// console.log('pickScript', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
script: e.detail
|
||||
})
|
||||
}}
|
||||
on:pickFlow={(e) => {
|
||||
// console.log('pickFlow', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
flow: e.detail
|
||||
})
|
||||
}}
|
||||
bind:open={menuOpen}
|
||||
/>
|
||||
</div>
|
||||
{#if data.enableTrigger}
|
||||
@@ -81,23 +91,34 @@
|
||||
class="edgeButtonContainer nodrag nopan"
|
||||
style:transform="translate(100%, 50%) translate({sourceX}px,{sourceY + 2}px)"
|
||||
>
|
||||
<InsertTriggerButton
|
||||
<InsertModuleButton
|
||||
disableAi={data.disableAi}
|
||||
on:new={(e) => {
|
||||
// console.log('new', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
detail: e.detail
|
||||
kind: e.detail.kind,
|
||||
inlineScript: e.detail.inlineScript
|
||||
})
|
||||
}}
|
||||
on:insert={(e) => {
|
||||
on:pickScript={(e) => {
|
||||
// console.log('pickScript', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
script: e.detail,
|
||||
detail: 'script'
|
||||
script: e.detail
|
||||
})
|
||||
}}
|
||||
on:pickFlow={(e) => {
|
||||
// console.log('pickFlow', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
flow: e.detail
|
||||
})
|
||||
}}
|
||||
kind="trigger"
|
||||
index={data?.index ?? 0}
|
||||
modules={data?.modules ?? []}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<script lang="ts">
|
||||
import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte'
|
||||
import { Cross } from 'lucide-svelte'
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { getStateColor } from '../../util'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
|
||||
|
||||
export let data: {
|
||||
insertable: boolean
|
||||
eventHandlers: GraphEventHandlers
|
||||
modules: FlowModule[]
|
||||
disableAi: boolean
|
||||
hasPreprocessor: boolean
|
||||
insertable: boolean
|
||||
modules: FlowModule[]
|
||||
moving: string | undefined
|
||||
eventHandlers: GraphEventHandlers
|
||||
index: number
|
||||
enableTrigger: boolean
|
||||
disableAi: boolean
|
||||
disableMoveIds: string[]
|
||||
}
|
||||
|
||||
const { selectedId } = getContext<{
|
||||
@@ -25,25 +28,29 @@
|
||||
<NodeWrapper let:darkMode>
|
||||
{#if data.insertable && !data.hasPreprocessor}
|
||||
<div class="absolute -top-8 left-1/2 transform -translate-x-1/2 z-10">
|
||||
<button
|
||||
on:click={(e) => {
|
||||
data.eventHandlers?.insert({
|
||||
<InsertModuleButton
|
||||
disableAi={data.disableAi}
|
||||
index={data.index ?? 0}
|
||||
modules={data?.modules ?? []}
|
||||
kind="preprocessor"
|
||||
on:new={(e) => {
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
kind: e.detail.kind,
|
||||
inlineScript: e.detail.inlineScript,
|
||||
detail: 'preprocessor'
|
||||
})
|
||||
}}
|
||||
title="Add preprocessor step"
|
||||
id={`flow-editor-add-preprocessor`}
|
||||
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 '
|
||||
)}
|
||||
>
|
||||
<Cross size={12} />
|
||||
</button>
|
||||
on:pickScript={(e) => {
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
script: e.detail,
|
||||
detail: 'preprocessor'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<VirtualItem
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
'/' +
|
||||
(state?.iteration_total ?? '?')
|
||||
: ''}
|
||||
bgColor={getStateColor(type, darkMode, true)}
|
||||
bgColor={getStateColor(type, darkMode, true, state?.skipped)}
|
||||
modules={data.modules ?? []}
|
||||
moving={data.moving}
|
||||
duration_ms={state?.duration_ms}
|
||||
|
||||
@@ -12,11 +12,16 @@ export const NODE = {
|
||||
export function getStateColor(
|
||||
state: FlowStatusModule['type'] | undefined,
|
||||
isDark: boolean,
|
||||
nonVirtualItem?: boolean
|
||||
nonVirtualItem?: boolean,
|
||||
isSkipped?: boolean
|
||||
): string {
|
||||
switch (state) {
|
||||
case 'Success':
|
||||
return isDark ? '#059669' : 'rgb(193, 255, 216)'
|
||||
if (isSkipped) {
|
||||
return isDark ? '#1E3A8A' : 'rgb(191, 219, 254)'
|
||||
} else {
|
||||
return isDark ? '#059669' : 'rgb(193, 255, 216)'
|
||||
}
|
||||
case 'Failure':
|
||||
return isDark ? '#dc2626' : 'rgb(248 113 113)'
|
||||
case 'InProgress':
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { Folder, User, Circle } from 'lucide-svelte'
|
||||
import { APP_TO_ICON_COMPONENT } from '../icons'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let filters: string[]
|
||||
export let selectedFilter:
|
||||
| { kind: 'owner' | 'integrations'; name: string | undefined }
|
||||
| undefined = undefined
|
||||
$: selectedAppFilter = selectedFilter?.kind === 'integrations' ? selectedFilter?.name : undefined
|
||||
|
||||
export let resourceType = false
|
||||
|
||||
function getIconComponent(name: string) {
|
||||
return APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if Array.isArray(filters) && filters.length > 0}
|
||||
{#each filters as filter (filter)}
|
||||
<div>
|
||||
<button
|
||||
class={twMerge(
|
||||
'w-full text-left py-2 px-3 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center rounded-md',
|
||||
filter === selectedAppFilter ? 'bg-surface-hover' : ''
|
||||
)}
|
||||
on:click={() => {
|
||||
selectedFilter =
|
||||
selectedAppFilter == filter ? undefined : { kind: 'integrations', name: filter }
|
||||
dispatch('selected')
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-center flex-row items-center gap-2">
|
||||
{#if resourceType}
|
||||
{@const icon = getIconComponent(filter)}
|
||||
{#if icon}
|
||||
<svelte:component this={icon} height="14px" width="14px" />
|
||||
{:else}
|
||||
<div
|
||||
class="w-[14px] h-[14px] text-gray-400 flex flex-row items-center justify-center"
|
||||
>
|
||||
<Circle size="12" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if filter.startsWith('u/')}
|
||||
<User class="mr-0.5" size={14} />
|
||||
{:else if filter.startsWith('f/')}
|
||||
<Folder class="mr-0.5" size={14} />
|
||||
{/if}
|
||||
<span class="text-left text-2xs text-primary font-normal">{filter}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -0,0 +1,154 @@
|
||||
<script lang="ts">
|
||||
export let white = false
|
||||
export let size = '24px'
|
||||
export let color: string | undefined = undefined
|
||||
export let spin: 'slow' | 'medium' | 'fast' | 'veryfast' | undefined = undefined
|
||||
|
||||
function hslToHex(h, s, l) {
|
||||
s /= 100
|
||||
l /= 100
|
||||
|
||||
let c = (1 - Math.abs(2 * l - 1)) * s
|
||||
let x = c * (1 - Math.abs(((h / 60) % 2) - 1))
|
||||
let m = l - c / 2
|
||||
let r = 0
|
||||
let g = 0
|
||||
let b = 0
|
||||
|
||||
if (0 <= h && h < 60) {
|
||||
r = c
|
||||
g = x
|
||||
b = 0
|
||||
} else if (60 <= h && h < 120) {
|
||||
r = x
|
||||
g = c
|
||||
b = 0
|
||||
} else if (120 <= h && h < 180) {
|
||||
r = 0
|
||||
g = c
|
||||
b = x
|
||||
} else if (180 <= h && h < 240) {
|
||||
r = 0
|
||||
g = x
|
||||
b = c
|
||||
} else if (240 <= h && h < 300) {
|
||||
r = x
|
||||
g = 0
|
||||
b = c
|
||||
} else if (300 <= h && h < 360) {
|
||||
r = c
|
||||
g = 0
|
||||
b = x
|
||||
}
|
||||
|
||||
let rs = Math.round((r + m) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
let gs = Math.round((g + m) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
let bs = Math.round((b + m) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, '0')
|
||||
|
||||
return `#${rs}${gs}${bs}`
|
||||
}
|
||||
|
||||
function hexToHsl(hex) {
|
||||
let r: number = parseInt(hex.slice(1, 3), 16) / 255
|
||||
let g: number = parseInt(hex.slice(3, 5), 16) / 255
|
||||
let b: number = parseInt(hex.slice(5, 7), 16) / 255
|
||||
|
||||
const max = Math.max(r, g, b),
|
||||
min = Math.min(r, g, b)
|
||||
let h,
|
||||
s,
|
||||
l = (max + min) / 2
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0 // Achromatic
|
||||
} else {
|
||||
const d = max - min
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0)
|
||||
break
|
||||
case g:
|
||||
h = (b - r) / d + 2
|
||||
break
|
||||
case b:
|
||||
h = (r - g) / d + 4
|
||||
break
|
||||
}
|
||||
h /= 6
|
||||
}
|
||||
|
||||
return [h * 360, s * 100, l * 100]
|
||||
}
|
||||
|
||||
function reduceSaturation(hex: string, reductionPercent: number) {
|
||||
// Convert HEX to HSL
|
||||
|
||||
// Convert the hex to HSL
|
||||
let [h, s, l] = hexToHsl(hex)
|
||||
|
||||
// Reduce the saturation by the specified percentage
|
||||
l = Math.max(0, l - reductionPercent)
|
||||
|
||||
// Convert back to hex
|
||||
return hslToHex(h, s, l)
|
||||
}
|
||||
|
||||
let lessSaturatedColor: string | undefined
|
||||
|
||||
$: color ? (lessSaturatedColor = reduceSaturation(color, -16)) : (lessSaturatedColor = undefined)
|
||||
</script>
|
||||
|
||||
<!-- SVG Icon with customizable color -->
|
||||
<svg
|
||||
class={$$props.class}
|
||||
class:animate-[spin_2s_linear_infinite]={spin === 'veryfast'}
|
||||
class:animate-[spin_5s_linear_infinite]={spin === 'fast'}
|
||||
class:animate-[spin_15s_linear_infinite]={spin === 'medium'}
|
||||
class:animate-[spin_50s_linear_infinite]={spin === 'slow'}
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 256 256"
|
||||
style="enable-background:new 0 0 256 256;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<!-- Use color or fallback to defaults (white or blue) -->
|
||||
<polygon
|
||||
fill={lessSaturatedColor || (white ? '#cccccc' : '#bcd4fc')}
|
||||
points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22"
|
||||
/>
|
||||
<polygon
|
||||
fill={color || (white ? '#ffffff' : '#3b82f6')}
|
||||
points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02"
|
||||
/>
|
||||
<polygon
|
||||
fill={color || (white ? '#ffffff' : '#3b82f6')}
|
||||
points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46"
|
||||
/>
|
||||
<polygon
|
||||
fill={color || (white ? '#ffffff' : '#3b82f6')}
|
||||
points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22"
|
||||
/>
|
||||
<polygon
|
||||
fill={lessSaturatedColor || (white ? '#cccccc' : '#bcd4fc')}
|
||||
points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21"
|
||||
/>
|
||||
<polygon
|
||||
fill={lessSaturatedColor || (white ? '#cccccc' : '#bcd4fc')}
|
||||
points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -18,6 +18,7 @@ import S3Icon from './S3Icon.svelte'
|
||||
import Slack from './Slack.svelte'
|
||||
import TogglIcon from './TogglIcon.svelte'
|
||||
import WindmillIcon from './WindmillIcon.svelte'
|
||||
import WindmillIcon2 from './WindmillIcon2.svelte'
|
||||
import MailchimpIcon from './MailchimpIcon.svelte'
|
||||
import SendgridIcon from './SendgridIcon.svelte'
|
||||
import SendflakeIcon from './SendflakeIcon.svelte'
|
||||
@@ -211,6 +212,7 @@ export {
|
||||
Slack,
|
||||
TogglIcon,
|
||||
WindmillIcon,
|
||||
WindmillIcon2,
|
||||
MailchimpIcon,
|
||||
SendgridIcon,
|
||||
LinkedinIcon,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
let script_path = ''
|
||||
let initialScriptPath = ''
|
||||
let fixedScriptPath = ''
|
||||
|
||||
let drawerLoading = true
|
||||
export async function openEdit(ePath: string, isFlow: boolean) {
|
||||
@@ -46,7 +47,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
export async function openNew(nis_flow: boolean, initial_script_path?: string) {
|
||||
export async function openNew(nis_flow: boolean, fixedScriptPath_?: string) {
|
||||
drawerLoading = true
|
||||
try {
|
||||
drawer?.openDrawer()
|
||||
@@ -58,8 +59,9 @@
|
||||
initialRoutePath = ''
|
||||
route_path = ''
|
||||
http_method = 'post'
|
||||
initialScriptPath = initial_script_path ?? ''
|
||||
script_path = initialScriptPath
|
||||
initialScriptPath = ''
|
||||
fixedScriptPath = fixedScriptPath_ ?? ''
|
||||
script_path = fixedScriptPath
|
||||
path = ''
|
||||
initialPath = ''
|
||||
dirtyPath = false
|
||||
@@ -282,8 +284,8 @@
|
||||
</p>
|
||||
<div class="flex flex-row mb-2">
|
||||
<ScriptPicker
|
||||
disabled={!can_write}
|
||||
initialPath={initialScriptPath}
|
||||
disabled={fixedScriptPath != '' || !can_write}
|
||||
initialPath={fixedScriptPath || initialScriptPath}
|
||||
kinds={['script']}
|
||||
allowFlow={true}
|
||||
bind:itemKind
|
||||
|
||||
@@ -350,6 +350,27 @@ export async function main() {
|
||||
}
|
||||
`
|
||||
|
||||
export const BUN_INIT_CODE_TRIGGER = `import * as wmill from "windmill-client"
|
||||
|
||||
export async function main() {
|
||||
|
||||
// A common trigger script would follow this pattern:
|
||||
// 1. Get the last saved state
|
||||
// const state = await wmill.getState()
|
||||
// 2. Get the actual state from the external service
|
||||
// const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json()
|
||||
// 3. Compare the two states and update the internal state
|
||||
// await wmill.setState(newState)
|
||||
// 4. Return the new rows
|
||||
// return range from (state to newState)
|
||||
|
||||
return [1,2,3]
|
||||
|
||||
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
|
||||
// 'flow_input.iter.value'
|
||||
}
|
||||
`
|
||||
|
||||
export const GO_INIT_CODE_TRIGGER = `package inner
|
||||
|
||||
import (
|
||||
@@ -715,7 +736,9 @@ export function initialCode(
|
||||
} else if (language == 'ansible') {
|
||||
return ANSIBLE_PLAYBOOK_INIT_CODE
|
||||
} else if (language == 'bun' || language == 'bunnative') {
|
||||
if (language == 'bunnative' || subkind === 'bunnative') {
|
||||
if (kind == 'trigger') {
|
||||
return BUN_INIT_CODE_TRIGGER
|
||||
} else if (language == 'bunnative' || subkind === 'bunnative') {
|
||||
return BUNNATIVE_INIT_CODE
|
||||
} else if (kind === 'approval') {
|
||||
return BUN_INIT_CODE_APPROVAL
|
||||
@@ -723,8 +746,7 @@ export function initialCode(
|
||||
return BUN_FAILURE_MODULE_CODE
|
||||
} else if (subkind === 'preprocessor') {
|
||||
return BUN_PREPROCESSOR_MODULE_CODE
|
||||
}
|
||||
if (subkind === 'flow') {
|
||||
} else if (subkind === 'flow') {
|
||||
return BUN_INIT_CODE_CLEAR
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { emptyString } from '$lib/utils'
|
||||
import { getUserExt } from '$lib/user'
|
||||
import { refreshSuperadmin } from '$lib/refreshUser'
|
||||
|
||||
let invites: WorkspaceInvite[] = []
|
||||
let list_all_as_super_admin: boolean = false
|
||||
@@ -45,6 +46,7 @@
|
||||
}
|
||||
|
||||
async function loadWorkspaces() {
|
||||
console.log('loading workspaces', $usersWorkspaceStore)
|
||||
if (!$usersWorkspaceStore) {
|
||||
try {
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
|
||||
@@ -76,7 +78,7 @@
|
||||
}
|
||||
$: list_all_as_super_admin != undefined && $userWorkspaces && handleListWorkspaces()
|
||||
|
||||
$: adminsInstance = workspaces?.find((x) => x.id == 'admins')
|
||||
$: adminsInstance = workspaces?.find((x) => x.id == 'admins') || $superadmin
|
||||
|
||||
$: nonAdminWorkspaces = (workspaces ?? []).filter((x) => x.id != 'admins')
|
||||
$: noWorkspaces = $superadmin && nonAdminWorkspaces.length == 0
|
||||
@@ -97,6 +99,7 @@
|
||||
getCreateWorkspaceRequireSuperadmin()
|
||||
}
|
||||
|
||||
refreshSuperadmin()
|
||||
loadInvites()
|
||||
loadWorkspaces()
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@
|
||||
saveDraft: () => {},
|
||||
initialPath: '',
|
||||
flowInputsStore: writable<FlowInput>({}),
|
||||
customUi: {}
|
||||
customUi: {},
|
||||
insertButtonOpen: writable(false)
|
||||
})
|
||||
|
||||
type LastEdit = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user