mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
Merge branch 'main' into bump-latest-stable-to-py313
This commit is contained in:
@@ -2,8 +2,8 @@ name: Backend check
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "backend/**"
|
||||
- ".github/workflows/backend-check.yml"
|
||||
- "backend/**"
|
||||
- ".github/workflows/backend-check.yml"
|
||||
|
||||
jobs:
|
||||
check_oss:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -32,16 +32,15 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: install xmlsec1
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -71,16 +70,14 @@ jobs:
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -110,8 +107,8 @@ jobs:
|
||||
|
||||
- name: install xmlsec1
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-dev libxmlsec1-dev
|
||||
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
@@ -120,7 +117,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
@@ -130,5 +127,3 @@ jobs:
|
||||
run: |
|
||||
mkdir -p fake_frontend_build
|
||||
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.83.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 30
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
|
||||
-c
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
$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,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Publish deno-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
env:
|
||||
repo: windmill-deno-client
|
||||
|
||||
jobs:
|
||||
build_deno_and_push_to_repo:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: generate_deno
|
||||
run: |
|
||||
cd deno-client
|
||||
rm .gitignore
|
||||
./build.sh
|
||||
- name: Pushes to another repository
|
||||
id: push_directory
|
||||
uses: cpina/github-action-push-to-another-repository@devel
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }}
|
||||
with:
|
||||
source-directory: deno-client/
|
||||
destination-github-username: ${{ github.repository_owner }}
|
||||
destination-repository-name: ${{ env.repo }}
|
||||
user-email: ruben@windmill.dev
|
||||
commit-message: See ORIGIN_COMMIT from $GITHUB_REF
|
||||
target-branch: main
|
||||
|
||||
tag_repo:
|
||||
needs: [build_deno_and_push_to_repo]
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/${{ env.repo }}
|
||||
token: ${{ secrets.DENO_PAT }}
|
||||
path: ./client
|
||||
|
||||
- name: Push client
|
||||
run: |
|
||||
cd ./client
|
||||
git config --global user.email "ruben@windmill.dev"
|
||||
git config --global user.name "rubenfiszel[bot]"
|
||||
git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}"
|
||||
git push --tags
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
|
||||
@@ -26,7 +26,11 @@ on:
|
||||
description: "Tag the image"
|
||||
required: true
|
||||
default: "test"
|
||||
|
||||
nsjail:
|
||||
description: "Build nsjail image (true, false)"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -42,6 +46,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
@@ -90,7 +95,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
@@ -100,10 +105,11 @@ jobs:
|
||||
|
||||
build_ee:
|
||||
runs-on: ubicloud
|
||||
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee)
|
||||
if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
@@ -152,7 +158,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -160,68 +166,6 @@ jobs:
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
build_ee_312:
|
||||
runs-on: ubicloud
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: windmill-labs/windmill-ee-private
|
||||
path: ./windmill-ee-private
|
||||
ref: ${{ env.ee_repo_ref }}
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public-py312
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-py312
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Substitute EE code
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public-py312.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
# disabled until we make it 100% reliable and add more meaningful tests
|
||||
# playwright:
|
||||
# runs-on: [self-hosted, new]
|
||||
@@ -263,7 +207,6 @@ jobs:
|
||||
ARCH: amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: |
|
||||
# pulling docker image with desired arch so that actions-docker-extract doesn't do it
|
||||
docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
|
||||
@@ -349,6 +292,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Prepare test run
|
||||
if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
|
||||
@@ -372,7 +316,7 @@ jobs:
|
||||
needs: [run_integration_test, build]
|
||||
if:
|
||||
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v'))
|
||||
startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -392,7 +336,7 @@ jobs:
|
||||
runs-on: ubicloud
|
||||
needs: [run_integration_test, build_ee]
|
||||
if:
|
||||
github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
|
||||
github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch') && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -412,7 +356,7 @@ jobs:
|
||||
verify_ee_image_vulnerabilities:
|
||||
runs-on: ubicloud
|
||||
needs: [tag_latest_ee]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -455,12 +399,13 @@ jobs:
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
if:
|
||||
(github.event_name != 'pull_request') && (github.event_name !=
|
||||
'workflow_dispatch')
|
||||
(github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail))
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
@@ -489,6 +434,11 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update Dockerfile image reference
|
||||
run: |
|
||||
sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail
|
||||
cat ./docker/DockerfileNsjail | grep "FROM"
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
@@ -505,7 +455,7 @@ jobs:
|
||||
build_ee_reports_privately:
|
||||
needs: [build_ee_nsjail]
|
||||
runs-on: ubicloud
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -600,6 +550,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
$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,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
+123
@@ -1,5 +1,128 @@
|
||||
# Changelog
|
||||
|
||||
## [1.454.1](https://github.com/windmill-labs/windmill/compare/v1.454.0...v1.454.1) (2025-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix codebase script execution ([b1155a9](https://github.com/windmill-labs/windmill/commit/b1155a9012c726c1b1c3f1a62f3aa96b8504681c))
|
||||
|
||||
## [1.454.0](https://github.com/windmill-labs/windmill/compare/v1.453.1...v1.454.0) (2025-01-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* history for flow inputs ([#5117](https://github.com/windmill-labs/windmill/issues/5117)) ([cd44056](https://github.com/windmill-labs/windmill/commit/cd440564d29aff39f6dc30ecbf0055696b07cf12))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix app form password handling ([4812c0c](https://github.com/windmill-labs/windmill/commit/4812c0cc9e46fdb5944f7010efdf617f2fd23cff))
|
||||
* improve codebase support ([c8cc9d2](https://github.com/windmill-labs/windmill/commit/c8cc9d2941dda2fedb959e1156c4f9b4f7dfab43))
|
||||
|
||||
## [1.453.1](https://github.com/windmill-labs/windmill/compare/v1.453.0...v1.453.1) (2025-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** fetching job result (db schema) separatly if too big ([#5171](https://github.com/windmill-labs/windmill/issues/5171)) ([f29ee67](https://github.com/windmill-labs/windmill/commit/f29ee67d9d8547d7c4a79e14698b7d88a722e730))
|
||||
* **typescript-client:** fix typescript client mocked api behavior ([7f516f0](https://github.com/windmill-labs/windmill/commit/7f516f08682876f5a7ca409ec002cba6ec0c5c5b))
|
||||
|
||||
## [1.453.0](https://github.com/windmill-labs/windmill/compare/v1.452.1...v1.453.0) (2025-01-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* custom response headers ([#5156](https://github.com/windmill-labs/windmill/issues/5156)) ([5544d42](https://github.com/windmill-labs/windmill/commit/5544d42a2db652660051b5f2e05f8db0102400fa))
|
||||
* **python:** add `custom_wheels` directory to `PYTHONPATH` ([#5169](https://github.com/windmill-labs/windmill/issues/5169)) ([c60f8da](https://github.com/windmill-labs/windmill/commit/c60f8dacf191c5255ef8fca462d6d3d571b86b07))
|
||||
* websocket trigger allow returning messages ([#5168](https://github.com/windmill-labs/windmill/issues/5168)) ([487c273](https://github.com/windmill-labs/windmill/commit/487c273bfbc5bf1731947c922f32cd026cca47f9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* oracledb tag for native workers + client not working on arm systems ([#5162](https://github.com/windmill-labs/windmill/issues/5162)) ([a1579c1](https://github.com/windmill-labs/windmill/commit/a1579c1654a2ddf9f6fc8f05f62b37bb09303752))
|
||||
* **python:** fix uv can't find ssl certificates ([#5157](https://github.com/windmill-labs/windmill/issues/5157)) ([680fb18](https://github.com/windmill-labs/windmill/commit/680fb1852a192e384e23e2f5ceea473383f9f5aa))
|
||||
|
||||
## [1.452.1](https://github.com/windmill-labs/windmill/compare/v1.452.0...v1.452.1) (2025-01-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix broken tabs for apps ([eaf633c](https://github.com/windmill-labs/windmill/commit/eaf633ce7675bf86108ebfc81ed735c26031ad71))
|
||||
|
||||
## [1.452.0](https://github.com/windmill-labs/windmill/compare/v1.451.0...v1.452.0) (2025-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* mssql executor supports azure db redirect ([#5150](https://github.com/windmill-labs/windmill/issues/5150)) ([fdb7122](https://github.com/windmill-labs/windmill/commit/fdb712234eb7f1a9374012d51e1cb25ee5aaa410))
|
||||
* **postgres, trigger:** support ssl ([#5149](https://github.com/windmill-labs/windmill/issues/5149)) ([e0b6aa4](https://github.com/windmill-labs/windmill/commit/e0b6aa42a28a4273f3a7bb7465396f2756e60a92))
|
||||
* test trigger connection ([#5145](https://github.com/windmill-labs/windmill/issues/5145)) ([d17397b](https://github.com/windmill-labs/windmill/commit/d17397b0703fb6a7dabf9db75d3c30ec26cdbe1e))
|
||||
|
||||
## [1.451.0](https://github.com/windmill-labs/windmill/compare/v1.450.1...v1.451.0) (2025-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* app builder accordion list and accordion tab component ([#5132](https://github.com/windmill-labs/windmill/issues/5132)) ([0e41023](https://github.com/windmill-labs/windmill/commit/0e41023889fb2f26ef5dc2a736dd59b819d92bf1))
|
||||
* **typescript-client:** add ability to mock typescript api for testing ([f9ce01c](https://github.com/windmill-labs/windmill/commit/f9ce01c1dcda581d943e3cf58008cc6c89d39c5f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** make argInput indent optional ([#5144](https://github.com/windmill-labs/windmill/issues/5144)) ([b8152a3](https://github.com/windmill-labs/windmill/commit/b8152a3259fa0f791990e41d825f196d82c4084d))
|
||||
|
||||
## [1.450.1](https://github.com/windmill-labs/windmill/compare/v1.450.0...v1.450.1) (2025-01-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix SCIM/SAML settings persistence ([697a060](https://github.com/windmill-labs/windmill/commit/697a060e3e01a183b6ac2371eee319b01ee891cf))
|
||||
|
||||
## [1.450.0](https://github.com/windmill-labs/windmill/compare/v1.449.3...v1.450.0) (2025-01-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** improve codebase support + remove --stateful + warn when pushing stale metadata ([#5139](https://github.com/windmill-labs/windmill/issues/5139)) ([b47e9c1](https://github.com/windmill-labs/windmill/commit/b47e9c14f277339ae51fd3a40e00e8ba4e8a1390))
|
||||
|
||||
## [1.449.3](https://github.com/windmill-labs/windmill/compare/v1.449.2...v1.449.3) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include postgres triggers in build [#5137](https://github.com/windmill-labs/windmill/issues/5137) ([27a25d4](https://github.com/windmill-labs/windmill/commit/27a25d4af9ca0f0e0ed1d06a462fdd7cf966ce2b))
|
||||
* update tests to rust 1.83.0 ([f29c702](https://github.com/windmill-labs/windmill/commit/f29c70279f14c2cdbf6e330ae266060f81f32bb6))
|
||||
|
||||
## [1.449.2](https://github.com/windmill-labs/windmill/compare/v1.449.1...v1.449.2) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update rust to 1.83.0 ([#5135](https://github.com/windmill-labs/windmill/issues/5135)) ([5381c76](https://github.com/windmill-labs/windmill/commit/5381c76b7d194d75f58d70b29aa3f5f8589fa899))
|
||||
|
||||
## [1.449.1](https://github.com/windmill-labs/windmill/compare/v1.449.0...v1.449.1) (2025-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix python refetching wheels from S3 ([#5133](https://github.com/windmill-labs/windmill/issues/5133)) ([2f1804b](https://github.com/windmill-labs/windmill/commit/2f1804b6d0e6f3492608967f8937d5e34481661b))
|
||||
|
||||
## [1.449.0](https://github.com/windmill-labs/windmill/compare/v1.448.1...v1.449.0) (2025-01-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** operator visibility setting per workspace ([#5124](https://github.com/windmill-labs/windmill/issues/5124)) ([1a1ea68](https://github.com/windmill-labs/windmill/commit/1a1ea682166af62133d3df2816490e3ebc92462f))
|
||||
* msft teams support for critical alerts ([#5113](https://github.com/windmill-labs/windmill/issues/5113)) ([a88fbb2](https://github.com/windmill-labs/windmill/commit/a88fbb238ae21ca53bccd53ccfd3c77e2946fda2))
|
||||
* postgres triggers ([#4860](https://github.com/windmill-labs/windmill/issues/4860)) ([316a216](https://github.com/windmill-labs/windmill/commit/316a2167e7648052afb3d7456c5ab9324348e56c))
|
||||
* **python:** Multiple runtime versions ([#4579](https://github.com/windmill-labs/windmill/issues/4579)) ([e47dd69](https://github.com/windmill-labs/windmill/commit/e47dd697f96883f8f97bf469e69d3344f12d9633))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bun, windows:** Fix "A required privilege is not held by the client." ([#5126](https://github.com/windmill-labs/windmill/issues/5126)) ([74bb660](https://github.com/windmill-labs/windmill/commit/74bb660b9799e79e31c33d96a4f06d77094287d5))
|
||||
|
||||
## [1.448.1](https://github.com/windmill-labs/windmill/compare/v1.448.0...v1.448.1) (2025-01-22)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
ARG DEBIAN_IMAGE=debian:bookworm-slim
|
||||
ARG RUST_IMAGE=rust:1.82-slim-bookworm
|
||||
ARG RUST_IMAGE=rust:1.83-slim-bookworm
|
||||
ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm
|
||||
|
||||
FROM ${RUST_IMAGE} AS rust_base
|
||||
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,6 +22,11 @@
|
||||
"ordinal": 3,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -33,8 +38,9 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736"
|
||||
"hash": "1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14"
|
||||
}
|
||||
+6
@@ -127,6 +127,11 @@
|
||||
"ordinal": 24,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -159,6 +164,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "websocket_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "http_routes_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "kafka_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "nats_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3"
|
||||
}
|
||||
+6
@@ -127,6 +127,11 @@
|
||||
"ordinal": 24,
|
||||
"name": "color",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "operator_settings",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -159,6 +164,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3"
|
||||
}
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as \"payload!: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "payload: _",
|
||||
"name": "payload!: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
@@ -71,9 +71,9 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4"
|
||||
"hash": "5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $10 AND path = $11",
|
||||
"query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $11 AND path = $12",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,11 +14,12 @@
|
||||
"Jsonb",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915"
|
||||
"hash": "63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select path from script where hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79"
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "last_ping",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85"
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH zombie_jobs AS (\n UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false \n RETURNING id, workspace_id, last_ping\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id, workspace_id, last_ping FROM zombie_jobs",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "last_ping",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357"
|
||||
}
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase AS \"codebase: String\" FROM script WHERE hash = $1 LIMIT 1",
|
||||
"query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option<ScriptLang>\", envs AS \"envs: Vec<String>\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -52,8 +52,8 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "codebase: String",
|
||||
"type_info": "Varchar"
|
||||
"name": "use_tar",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -66,8 +66,8 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664"
|
||||
"hash": "d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799"
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload!: _\", trigger_extra as \"trigger_extra: _\" FROM capture WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "trigger_kind: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "trigger_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"webhook",
|
||||
"http",
|
||||
"websocket",
|
||||
"kafka",
|
||||
"email",
|
||||
"nats"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "payload!: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "trigger_extra: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7"
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value, is_secret\n FROM variable\n WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "is_secret",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_version.value->>'early_return' as early_return\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1"
|
||||
}
|
||||
Generated
+209
-103
@@ -67,7 +67,7 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -651,9 +651,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.14"
|
||||
version = "1.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f40e82e858e02445402906e454a73e244c7f501fcae198977585946c48e8697"
|
||||
checksum = "dc47e70fc35d054c8fcd296d47a61711f043ac80534a10b4f741904f81e73a90"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -718,9 +718,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.55.0"
|
||||
version = "1.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33993c0b054f4251ff2946941b56c26b582677303eeca34087594eb901ece022"
|
||||
checksum = "c54bab121fe1881a74c338c5f723d1592bf3b53167f80268a1274f404e1acc38"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -740,9 +740,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.56.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bd3ceba74a584337a8f3839c818f14f1a2288bfd24235120ff22d7e17a0dd54"
|
||||
checksum = "8c8234fd024f7ac61c4e44ea008029bde934250f371efe7d4a39708397b1080c"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -762,9 +762,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.56.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07835598e52dd354368429cb2abf447ce523ea446d0a533a63cb42cd0d2d9280"
|
||||
checksum = "ba60e1d519d6f23a9df712c04fdeadd7872ac911c84b2f62a8bda92e129b7962"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -962,7 +962,7 @@ dependencies = [
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
@@ -1282,7 +1282,7 @@ dependencies = [
|
||||
"hex",
|
||||
"http 1.2.0",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-named-pipe",
|
||||
"hyper-util",
|
||||
"hyperlocal",
|
||||
@@ -1388,9 +1388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
@@ -1708,9 +1708,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.52"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e"
|
||||
checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -1893,9 +1893,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -1941,7 +1941,7 @@ checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"once_cell",
|
||||
"winnow 0.6.24",
|
||||
"winnow 0.6.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2641,7 +2641,7 @@ dependencies = [
|
||||
"hickory-resolver",
|
||||
"http 1.2.0",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.27.5",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@@ -2661,9 +2661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deno_media_type"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "577fe2bbe04f3e9b1b7c6fac6a75101a9fbd611c50a6b68789e69f4d63dcb2b4"
|
||||
checksum = "600222d059ab31ff31182b3e12615df2134a9e01605836b78ad8df91ba39eab3"
|
||||
dependencies = [
|
||||
"data-url",
|
||||
"serde",
|
||||
@@ -3764,6 +3764,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.13.3+wasi-0.2.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
@@ -3944,6 +3956,17 @@ dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashify"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
@@ -4081,9 +4104,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hstr"
|
||||
version = "0.2.15"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63d6824358c0fd9a68bb23999ed2ef76c84f79408a26ef7ae53d5f370c94ad36"
|
||||
checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"new_debug_unreachable",
|
||||
@@ -4178,9 +4201,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.5"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
@@ -4220,9 +4243,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.5.2"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
|
||||
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -4246,7 +4269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -4278,7 +4301,7 @@ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.21",
|
||||
"rustls-native-certs 0.8.1",
|
||||
@@ -4295,7 +4318,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
|
||||
dependencies = [
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -4323,7 +4346,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
@@ -4342,7 +4365,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
@@ -4359,7 +4382,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -4564,9 +4587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.9"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
|
||||
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||
dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
@@ -5037,11 +5060,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mail-parser"
|
||||
version = "0.9.4"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
|
||||
checksum = "187a2b93c4c8c32f552ee06c2d99915e575de2fc7e04b07891c9edfee5b8edd6"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"hashify",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -5064,9 +5088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "malachite"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5616515d632967cd329b6f6db96be9a03ea0b3a49cdbc45b0016803dad8a77b7"
|
||||
checksum = "4a6ecab92657eb234bfe98abd0b17920772c6b14ce69256950142e2eb36d000b"
|
||||
dependencies = [
|
||||
"malachite-base",
|
||||
"malachite-nz",
|
||||
@@ -5075,9 +5099,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "malachite-base"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b"
|
||||
checksum = "06f6d078bb4dad5b76f6a85737e6c9113667d199612a73eef375c56e75d5d4d5"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"itertools 0.11.0",
|
||||
@@ -5100,9 +5124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "malachite-nz"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1503b27e825cabd1c3d0ff1e95a39fb2ec9eab6fd3da6cfa41aec7091d273e78"
|
||||
checksum = "ad61a09cde72dcdb8b2baaf3e71430c5bd94fb6753da89f17301a459f3e466db"
|
||||
dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"libm",
|
||||
@@ -5111,9 +5135,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "malachite-q"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a475503a70a3679dbe3b9b230a23622516742528ba614a7b2490f180ea9cb514"
|
||||
checksum = "be4a9dfffb87667ae94e8320213d3f5419e3e37311bc6bf8f4d2ab9b44f3a535"
|
||||
dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"malachite-base",
|
||||
@@ -5388,9 +5412,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -5642,7 +5666,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures",
|
||||
"humantime",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"itertools 0.13.0",
|
||||
"md-5 0.10.6",
|
||||
"parking_lot",
|
||||
@@ -5753,9 +5777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.68"
|
||||
version = "0.10.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||
checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cfg-if",
|
||||
@@ -5779,9 +5803,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
@@ -6274,6 +6298,17 @@ dependencies = [
|
||||
"tokio-postgres 0.7.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres-native-tls"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-postgres 0.7.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres-protocol"
|
||||
version = "0.6.7"
|
||||
@@ -6348,7 +6383,7 @@ version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6755,6 +6790,17 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.0",
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -6775,6 +6821,16 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@@ -6793,6 +6849,16 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.3"
|
||||
@@ -7052,7 +7118,7 @@ dependencies = [
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.27.5",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
@@ -7401,9 +7467,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.10.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
|
||||
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
@@ -7506,9 +7572,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
|
||||
[[package]]
|
||||
name = "ryu-js"
|
||||
@@ -7776,9 +7842,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.137"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"itoa",
|
||||
@@ -9233,13 +9299,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.15.0"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
||||
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -9762,7 +9828,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.24",
|
||||
"winnow 0.6.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9780,7 +9846,7 @@ dependencies = [
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"hyper-timeout",
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
@@ -10280,9 +10346,9 @@ checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -10554,6 +10620,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
@@ -10785,7 +10860,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -10802,7 +10877,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"prometheus",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.9",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10815,6 +10890,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.12.1",
|
||||
"v8",
|
||||
"windmill-api",
|
||||
"windmill-api-client",
|
||||
"windmill-autoscaling",
|
||||
@@ -10827,7 +10903,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -10857,7 +10933,7 @@ dependencies = [
|
||||
"hf-hub",
|
||||
"hmac",
|
||||
"http 1.2.0",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"itertools 0.14.0",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
@@ -10872,9 +10948,10 @@ dependencies = [
|
||||
"openssl",
|
||||
"pg_escape",
|
||||
"pin-project",
|
||||
"postgres-native-tls 0.5.0 (git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b)",
|
||||
"prometheus",
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"rdkafka",
|
||||
"regex",
|
||||
"reqwest 0.12.9",
|
||||
@@ -10919,7 +10996,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -10927,7 +11004,7 @@ dependencies = [
|
||||
"prettyplease 0.1.25",
|
||||
"progenitor",
|
||||
"progenitor-client",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10937,7 +11014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -10950,7 +11027,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -10964,7 +11041,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -10984,7 +11061,7 @@ dependencies = [
|
||||
"git-version",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hyper 1.5.2",
|
||||
"hyper 1.6.0",
|
||||
"indexmap 2.7.1",
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -10999,7 +11076,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"prometheus",
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"reqwest 0.12.9",
|
||||
"semver 1.0.25",
|
||||
@@ -11023,7 +11100,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -11037,7 +11114,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -11060,7 +11137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -11072,7 +11149,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -11081,7 +11158,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11093,7 +11170,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -11105,7 +11182,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -11117,7 +11194,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11129,7 +11206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -11140,7 +11217,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -11151,7 +11228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11169,7 +11246,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -11186,7 +11263,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11198,7 +11275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11216,7 +11293,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.15",
|
||||
@@ -11238,7 +11315,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -11248,7 +11325,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11281,7 +11358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -11291,7 +11368,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11331,9 +11408,9 @@ dependencies = [
|
||||
"opentelemetry",
|
||||
"oracle",
|
||||
"pem 3.0.4",
|
||||
"postgres-native-tls",
|
||||
"postgres-native-tls 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prometheus",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"reqwest 0.12.9",
|
||||
"rust_decimal",
|
||||
@@ -11566,9 +11643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.24"
|
||||
version = "0.6.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
||||
checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -11589,6 +11666,15 @@ version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
@@ -11682,7 +11768,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11696,6 +11791,17 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.5"
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -30,7 +30,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.448.1"
|
||||
version = "1.454.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -61,7 +61,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", "windmill-indexer/enterprise", "windmill-indexer/parquet", "enterprise", "parquet"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:deno_core"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"]
|
||||
kafka = ["windmill-api/kafka"]
|
||||
nats = ["windmill-api/nats"]
|
||||
otel = ["windmill-common/otel", "windmill-worker/otel"]
|
||||
@@ -116,6 +116,7 @@ deno_core = { workspace = true, optional = true }
|
||||
object_store = { workspace = true, optional = true }
|
||||
quote.workspace = true
|
||||
memchr.workspace = true
|
||||
v8 = { workspace = true, optional = true }
|
||||
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
@@ -133,6 +134,7 @@ 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" }
|
||||
@@ -157,6 +159,7 @@ windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
|
||||
v8 = "=130.0.7" # Exact version
|
||||
memchr = "2.7.4"
|
||||
axum = { version = "^0.7", features = ["multipart"] }
|
||||
headers = "^0"
|
||||
@@ -265,6 +268,7 @@ convert_case = "0.6.0"
|
||||
getrandom = "0.2"
|
||||
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
|
||||
rust-postgres = { package = "tokio-postgres", git = "https://github.com/imor/rust-postgres", rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b"}
|
||||
rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/imor/rust-postgres", features = ["runtime"], rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b" }
|
||||
bit-vec = "=0.6.3"
|
||||
mappable-rc = "^0"
|
||||
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0c89b8974ff6e1c9eda2134f09d4a03f18b57c15
|
||||
7cc103e5827b1ec91180a5939a9c022a2a1856c6
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE workspace_settings DROP COLUMN operator_settings;
|
||||
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE workspace_settings ADD COLUMN operator_settings JSONB DEFAULT '{
|
||||
"runs": true,
|
||||
"groups": true,
|
||||
"folders": true,
|
||||
"workers": true,
|
||||
"triggers": true,
|
||||
"resources": true,
|
||||
"schedules": true,
|
||||
"variables": true,
|
||||
"audit_logs": true
|
||||
}';
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cloud_workspace_settings DROP COLUMN last_warning_sent;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cloud_workspace_settings ADD COLUMN last_warning_sent TIMESTAMP NULL;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["oracledb"]'::jsonb) where name = 'worker__native' and config @> '{"worker_tags": ["nativets", "postgresql", "mysql", "graphql", "snowflake", "bigquery", "mssql"]}'::jsonb AND NOT config->'worker_tags' @> '"oracledb"'::jsonb;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE websocket_trigger DROP COLUMN can_return_message;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE websocket_trigger ADD COLUMN can_return_message BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -37,6 +37,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
|
||||
"smb" => "pysmb",
|
||||
"PIL" => "Pillow",
|
||||
"googleapiclient" => "google-api-python-client",
|
||||
"googlecloudbigquery" => "google-cloud-bigquery",
|
||||
"dateutil" => "python-dateutil",
|
||||
"mailparser" => "mail-parser",
|
||||
"mailparser-reply" => "mail-parser-reply",
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1737885589,
|
||||
"narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1736320768,
|
||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1738117527,
|
||||
"narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "6a3dc6ce4132bd57359214d986db376f2333c14d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
rust-overlay,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [(import rust-overlay)];
|
||||
};
|
||||
rust = pkgs.rust-bin.nightly.latest.default.override {
|
||||
extensions = [
|
||||
"rust-src"
|
||||
];
|
||||
targets = ["wasm32-unknown-unknown"];
|
||||
};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rust
|
||||
nodejs
|
||||
wasm-pack
|
||||
sccache
|
||||
];
|
||||
RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache";
|
||||
CARGO_PATH = "${rust}/bin/cargo";
|
||||
};
|
||||
});
|
||||
}
|
||||
+10
-9
@@ -36,12 +36,13 @@ use windmill_common::{
|
||||
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
|
||||
DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
|
||||
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
|
||||
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
|
||||
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING,
|
||||
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
|
||||
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING, TEAMS_SETTING
|
||||
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
|
||||
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_ee::schedule_stats,
|
||||
@@ -788,7 +789,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
EXPOSE_METRICS_SETTING => {
|
||||
tracing::info!("Metrics setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..40);
|
||||
let rd_delay = rand::rng().random_range(0..40);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
@@ -802,7 +803,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
OTEL_SETTING => {
|
||||
tracing::info!("OTEL setting changed, restarting");
|
||||
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..4);
|
||||
let rd_delay = rand::rng().random_range(0..4);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill");
|
||||
@@ -812,7 +813,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
if server_mode {
|
||||
tracing::info!("Request limit size change detected, killing server expecting to be restarted");
|
||||
// we wait a bit randomly to avoid having all servers shutdown at same time
|
||||
let rd_delay = rand::thread_rng().gen_range(0..4);
|
||||
let rd_delay = rand::rng().random_range(0..4);
|
||||
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
|
||||
if let Err(e) = tx.send(()) {
|
||||
tracing::error!(error = %e, "Could not send killpill to server");
|
||||
|
||||
+34
-4
@@ -1506,9 +1506,20 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> {
|
||||
async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker_name: &str) {
|
||||
if *RESTART_ZOMBIE_JOBS {
|
||||
let restarted = sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null
|
||||
WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping",
|
||||
"WITH zombie_jobs AS (
|
||||
UPDATE queue SET running = false, started_at = null
|
||||
WHERE last_ping < now() - ($1 || ' seconds')::interval
|
||||
AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false
|
||||
RETURNING id, workspace_id, last_ping
|
||||
),
|
||||
update_concurrency AS (
|
||||
UPDATE concurrency_counter cc
|
||||
SET job_uuids = job_uuids - zj.id::text
|
||||
FROM zombie_jobs zj
|
||||
INNER JOIN concurrency_key ck ON ck.job_id = zj.id
|
||||
WHERE cc.concurrency_id = ck.key
|
||||
)
|
||||
SELECT id, workspace_id, last_ping FROM zombie_jobs",
|
||||
*ZOMBIE_JOB_TIMEOUT,
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -1652,12 +1663,31 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
|
||||
tracing::error!(error_message);
|
||||
report_critical_error(error_message, db.clone(), Some(&flow.workspace_id), None).await;
|
||||
// if the flow hasn't started and is a zombie, we can simply restart it
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let concurrency_key =
|
||||
sqlx::query_scalar!("SELECT key FROM concurrency_key WHERE job_id = $1", flow.id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if let Some(key) = concurrency_key {
|
||||
sqlx::query!(
|
||||
"UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1",
|
||||
key,
|
||||
flow.id.hyphenated().to_string()
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false",
|
||||
flow.id
|
||||
)
|
||||
.execute(db)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
} else {
|
||||
let id = flow.id.clone();
|
||||
let last_ping = flow.last_ping.clone();
|
||||
|
||||
@@ -28,7 +28,7 @@ zip = ["dep:async_zip"]
|
||||
oauth2 = ["dep:async-oauth2"]
|
||||
http_trigger = ["dep:matchit"]
|
||||
static_frontend = ["dep:rust-embed"]
|
||||
postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal"]
|
||||
postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -116,4 +116,5 @@ rust-postgres = { workspace = true, optional = true }
|
||||
pg_escape = { workspace = true, optional = true }
|
||||
byteorder = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true, optional = true }
|
||||
rust_decimal = { workspace = true, optional = true }
|
||||
rust_decimal = { workspace = true, optional = true }
|
||||
rust-postgres-native-tls = { workspace = true, optional = true}
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.448.1
|
||||
version: 1.454.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -1618,6 +1618,29 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/User"
|
||||
|
||||
/w/{workspace}/workspaces/operator_settings:
|
||||
post:
|
||||
operationId: updateOperatorSettings
|
||||
summary: Update operator settings for a workspace
|
||||
description: Updates the operator settings for a specific workspace. Requires workspace admin privileges.
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OperatorSettings"
|
||||
responses:
|
||||
'200':
|
||||
description: Operator settings updated successfully
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/exists/{email}:
|
||||
get:
|
||||
summary: exists email
|
||||
@@ -1745,6 +1768,8 @@ paths:
|
||||
type: boolean
|
||||
color:
|
||||
type: string
|
||||
operator_settings:
|
||||
$ref: "#/components/schemas/OperatorSettings"
|
||||
required:
|
||||
- code_completion_enabled
|
||||
- automatic_billing
|
||||
@@ -7918,6 +7943,39 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/websocket_triggers/test:
|
||||
post:
|
||||
summary: test websocket connection
|
||||
operationId: testWebsocketConnection
|
||||
tags:
|
||||
- websocket_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: test websocket connection
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
url_runnable_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
can_return_message:
|
||||
type: boolean
|
||||
required:
|
||||
- url
|
||||
- can_return_message
|
||||
responses:
|
||||
"200":
|
||||
description: successfuly connected to websocket
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/create:
|
||||
post:
|
||||
summary: create kafka trigger
|
||||
@@ -8079,6 +8137,34 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/test:
|
||||
post:
|
||||
summary: test kafka connection
|
||||
operationId: testKafkaConnection
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: test kafka connection
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
connection:
|
||||
type: object
|
||||
required:
|
||||
- connection
|
||||
responses:
|
||||
"200":
|
||||
description: successfuly connected to kafka brokers
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/nats_triggers/create:
|
||||
post:
|
||||
summary: create nats trigger
|
||||
@@ -8242,6 +8328,35 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/nats_triggers/test:
|
||||
post:
|
||||
summary: test NATS connection
|
||||
operationId: testNatsConnection
|
||||
tags:
|
||||
- nats_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: test nats connection
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
connection:
|
||||
type: object
|
||||
required:
|
||||
- connection
|
||||
responses:
|
||||
"200":
|
||||
description: successfuly connected to NATS servers
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}:
|
||||
get:
|
||||
summary: check if postgres configuration is set to logical
|
||||
@@ -9712,6 +9827,25 @@ paths:
|
||||
$ref: "#/components/schemas/Capture"
|
||||
|
||||
/w/{workspace}/capture/{id}:
|
||||
get:
|
||||
summary: get a capture
|
||||
operationId: getCapture
|
||||
tags:
|
||||
- capture
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: capture
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Capture"
|
||||
delete:
|
||||
summary: delete a capture
|
||||
operationId: deleteCapture
|
||||
@@ -9980,6 +10114,8 @@ paths:
|
||||
properties:
|
||||
connection_settings_str:
|
||||
type: string
|
||||
azure_container_path:
|
||||
type: string
|
||||
required:
|
||||
- connection_settings_str
|
||||
|
||||
@@ -13008,6 +13144,8 @@ components:
|
||||
$ref: "#/components/schemas/WebsocketTriggerInitialMessage"
|
||||
url_runnable_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
can_return_message:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -13021,6 +13159,7 @@ components:
|
||||
- workspace_id
|
||||
- enabled
|
||||
- filters
|
||||
- can_return_message
|
||||
|
||||
NewWebsocketTrigger:
|
||||
type: object
|
||||
@@ -13052,6 +13191,8 @@ components:
|
||||
$ref: "#/components/schemas/WebsocketTriggerInitialMessage"
|
||||
url_runnable_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
can_return_message:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -13059,6 +13200,7 @@ components:
|
||||
- url
|
||||
- is_flow
|
||||
- filters
|
||||
- can_return_message
|
||||
|
||||
EditWebsocketTrigger:
|
||||
type: object
|
||||
@@ -13088,6 +13230,8 @@ components:
|
||||
$ref: "#/components/schemas/WebsocketTriggerInitialMessage"
|
||||
url_runnable_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
can_return_message:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -13095,7 +13239,7 @@ components:
|
||||
- url
|
||||
- is_flow
|
||||
- filters
|
||||
|
||||
- can_return_message
|
||||
WebsocketTriggerInitialMessage:
|
||||
anyOf:
|
||||
- type: object
|
||||
@@ -13221,6 +13365,9 @@ components:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
last_server_ping:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -13640,6 +13787,8 @@ components:
|
||||
type: string
|
||||
color:
|
||||
type: string
|
||||
operator_settings:
|
||||
$ref: "#/components/schemas/OperatorSettings"
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
@@ -14498,6 +14647,48 @@ components:
|
||||
required:
|
||||
- trigger_kind
|
||||
|
||||
OperatorSettings:
|
||||
nullable: true
|
||||
type: object
|
||||
required:
|
||||
- runs
|
||||
- schedules
|
||||
- resources
|
||||
- variables
|
||||
- triggers
|
||||
- audit_logs
|
||||
- groups
|
||||
- folders
|
||||
- workers
|
||||
properties:
|
||||
runs:
|
||||
type: boolean
|
||||
description: Whether operators can view runs
|
||||
schedules:
|
||||
type: boolean
|
||||
description: Whether operators can view schedules
|
||||
resources:
|
||||
type: boolean
|
||||
description: Whether operators can view resources
|
||||
variables:
|
||||
type: boolean
|
||||
description: Whether operators can view variables
|
||||
audit_logs:
|
||||
type: boolean
|
||||
description: Whether operators can view audit logs
|
||||
triggers:
|
||||
type: boolean
|
||||
description: Whether operators can view triggers
|
||||
groups:
|
||||
type: boolean
|
||||
description: Whether operators can view groups page
|
||||
folders:
|
||||
type: boolean
|
||||
description: Whether operators can view folders page
|
||||
workers:
|
||||
type: boolean
|
||||
description: Whether operators can view workers page
|
||||
|
||||
TeamInfo:
|
||||
type: object
|
||||
required:
|
||||
@@ -14518,7 +14709,7 @@ components:
|
||||
description: List of channels within the team
|
||||
items:
|
||||
$ref: '#/components/schemas/ChannelInfo'
|
||||
|
||||
|
||||
ChannelInfo:
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -16,7 +16,6 @@ use crate::{
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
use windmill_common::variables::encrypt;
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{
|
||||
job_helpers_ee::{
|
||||
@@ -54,6 +53,7 @@ use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
use windmill_common::variables::encrypt;
|
||||
use windmill_common::{
|
||||
apps::{AppScriptId, ListAppQuery},
|
||||
cache::{self, future::FutureCachedExt},
|
||||
|
||||
@@ -35,9 +35,9 @@ use windmill_queue::{PushArgs, PushArgsOwned};
|
||||
#[cfg(feature = "http_trigger")]
|
||||
use crate::http_triggers::{build_http_trigger_extra, HttpMethod};
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
use crate::kafka_triggers_ee::KafkaResourceSecurity;
|
||||
use crate::kafka_triggers_ee::KafkaTriggerConfigConnection;
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
use crate::nats_triggers_ee::NatsResourceAuth;
|
||||
use crate::nats_triggers_ee::NatsTriggerConfigConnection;
|
||||
use crate::{
|
||||
args::WebhookArgs,
|
||||
db::{ApiAuthed, DB},
|
||||
@@ -56,6 +56,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_configs/:runnable_kind/*path", get(get_configs))
|
||||
.route("/list/:runnable_kind/*path", get(list_captures))
|
||||
.route("/:id", delete(delete_capture))
|
||||
.route("/:id", get(get_capture))
|
||||
}
|
||||
|
||||
pub fn workspaced_unauthed_service() -> Router {
|
||||
@@ -110,14 +111,6 @@ struct HttpTriggerConfig {
|
||||
http_method: HttpMethod,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum KafkaTriggerConfigConnection {
|
||||
Resource { kafka_resource_path: String },
|
||||
Static { brokers: Vec<String>, security: KafkaResourceSecurity },
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct KafkaTriggerConfig {
|
||||
@@ -127,14 +120,6 @@ pub struct KafkaTriggerConfig {
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum NatsTriggerConfigConnection {
|
||||
Resource { nats_resource_path: String },
|
||||
Static { servers: Vec<String>, auth: NatsResourceAuth, require_tls: bool },
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NatsTriggerConfig {
|
||||
@@ -296,7 +281,7 @@ async fn list_captures(
|
||||
|
||||
let captures = sqlx::query_as!(
|
||||
Capture,
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload: _", trigger_extra as "trigger_extra: _"
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '"WINDMILL_TOO_BIG"'::jsonb END as "payload!: _", trigger_extra as "trigger_extra: _"
|
||||
FROM capture
|
||||
WHERE workspace_id = $1
|
||||
AND path = $2 AND is_flow = $3
|
||||
@@ -319,6 +304,24 @@ async fn list_captures(
|
||||
Ok(Json(captures))
|
||||
}
|
||||
|
||||
async fn get_capture(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, id)): Path<(String, i64)>,
|
||||
) -> JsonResult<Capture> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let capture = sqlx::query_as!(
|
||||
Capture,
|
||||
r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE id = $1 AND workspace_id = $2"#,
|
||||
id,
|
||||
&w_id,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(capture))
|
||||
}
|
||||
|
||||
async fn delete_capture(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -521,7 +521,7 @@ async fn get_http_route_trigger(
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override.unwrap_or("anonymous".to_string())),
|
||||
Some(username_override.unwrap_or(format!("http-{}", trigger.path))),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -685,12 +685,6 @@ async fn route_job(
|
||||
.await,
|
||||
);
|
||||
|
||||
let label_prefix = Some(format!(
|
||||
"http-{}-{}-",
|
||||
method.as_str().to_lowercase(),
|
||||
trigger.route_path
|
||||
));
|
||||
|
||||
let run_query = RunJobQuery::default();
|
||||
|
||||
if trigger.is_flow {
|
||||
@@ -703,7 +697,7 @@ async fn route_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -716,7 +710,7 @@ async fn route_job(
|
||||
user_db,
|
||||
args,
|
||||
trigger.workspace_id.clone(),
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -731,7 +725,7 @@ async fn route_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
@@ -744,7 +738,7 @@ async fn route_job(
|
||||
user_db,
|
||||
trigger.workspace_id.clone(),
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.into_response()
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::HeaderValue;
|
||||
use futures::TryFutureExt;
|
||||
use http::{HeaderMap, HeaderName};
|
||||
use itertools::Itertools;
|
||||
use quick_cache::sync::Cache;
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::Pool;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
#[cfg(feature = "prometheus")]
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -1966,20 +1968,34 @@ async fn resume_suspended_job_internal(
|
||||
resume_immediately_if_relevant(parent_flow_info, job_id, &mut tx).await?;
|
||||
}
|
||||
|
||||
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
|
||||
|
||||
let audit_author = match authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => {
|
||||
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
|
||||
AuditAuthor { email: approver.clone(), username: approver, username_override: None }
|
||||
}
|
||||
None => AuditAuthor {
|
||||
email: approver.clone(),
|
||||
username: approver.clone(),
|
||||
username_override: None,
|
||||
},
|
||||
};
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&audit_author,
|
||||
"jobs.approved",
|
||||
"jobs.suspend_resume",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&job_id.to_string()),
|
||||
Some(
|
||||
&serde_json::json!({
|
||||
"approved": approved,
|
||||
"job_id": job_id,
|
||||
"details": if approved {
|
||||
format!("Approved by {}", &approver)
|
||||
} else {
|
||||
format!("Cancelled by {}", &approver)
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -3522,15 +3538,17 @@ lazy_static::lazy_static! {
|
||||
pub struct WindmillCompositeResult {
|
||||
windmill_status_code: Option<u16>,
|
||||
windmill_content_type: Option<String>,
|
||||
windmill_headers: Option<HashMap<String, String>>,
|
||||
result: Option<Box<RawValue>>,
|
||||
}
|
||||
pub async fn run_wait_result(
|
||||
|
||||
pub async fn run_wait_result_internal(
|
||||
db: &DB,
|
||||
uuid: Uuid,
|
||||
w_id: String,
|
||||
node_id_for_empty_return: Option<String>,
|
||||
username: &str,
|
||||
) -> error::Result<Response> {
|
||||
) -> error::Result<(Box<RawValue>, bool)> {
|
||||
let mut result = None;
|
||||
let mut success = false;
|
||||
let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600);
|
||||
@@ -3602,81 +3620,110 @@ pub async fn run_wait_result(
|
||||
};
|
||||
tokio::time::sleep(core::time::Duration::from_millis(delay)).await;
|
||||
}
|
||||
|
||||
if let Some(result) = result {
|
||||
g.done = true;
|
||||
Ok((result, success))
|
||||
} else {
|
||||
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
|
||||
}
|
||||
}
|
||||
|
||||
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
|
||||
match composite_result {
|
||||
Ok(WindmillCompositeResult {
|
||||
windmill_status_code,
|
||||
windmill_content_type,
|
||||
result: result_value,
|
||||
}) => {
|
||||
if windmill_content_type.is_none() && windmill_status_code.is_none() {
|
||||
return Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
pub async fn run_wait_result(
|
||||
db: &DB,
|
||||
uuid: Uuid,
|
||||
w_id: String,
|
||||
node_id_for_empty_return: Option<String>,
|
||||
username: &str,
|
||||
) -> error::Result<Response> {
|
||||
let (result, success) =
|
||||
run_wait_result_internal(db, uuid, w_id, node_id_for_empty_return, username).await?;
|
||||
|
||||
let status_code_or_default = windmill_status_code
|
||||
.map(|val| match StatusCode::from_u16(val) {
|
||||
Ok(sc) => Ok(sc),
|
||||
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if !success {
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
} else if result_value.is_some() {
|
||||
Ok(StatusCode::OK)
|
||||
} else {
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
})?;
|
||||
|
||||
if windmill_content_type.is_some() {
|
||||
let serialized_json_result = result_value
|
||||
.map(|val| val.get().to_owned())
|
||||
.unwrap_or_else(String::new);
|
||||
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
|
||||
// it falls back to the original serialized JSON if it doesn't work.
|
||||
let serialized_result =
|
||||
serde_json::from_str::<String>(serialized_json_result.as_str())
|
||||
.ok()
|
||||
.unwrap_or(serialized_json_result);
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
[(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(windmill_content_type.unwrap().as_str()).unwrap(),
|
||||
)],
|
||||
serialized_result,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
let composite_result = serde_json::from_str::<WindmillCompositeResult>(result.get());
|
||||
match composite_result {
|
||||
Ok(WindmillCompositeResult {
|
||||
windmill_status_code,
|
||||
windmill_content_type,
|
||||
windmill_headers,
|
||||
result: result_value,
|
||||
}) => {
|
||||
if windmill_content_type.is_none()
|
||||
&& windmill_status_code.is_none()
|
||||
&& windmill_headers.is_none()
|
||||
{
|
||||
return Ok((
|
||||
status_code_or_default,
|
||||
Json(result_value), // default to JSON result if no content type is provided
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
_ => Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response()),
|
||||
|
||||
let status_code_or_default = windmill_status_code
|
||||
.map(|val| match StatusCode::from_u16(val) {
|
||||
Ok(sc) => Ok(sc),
|
||||
Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if !success {
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
} else if result_value.is_some() {
|
||||
Ok(StatusCode::OK)
|
||||
} else {
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
if let Some(windmill_headers) = windmill_headers {
|
||||
for (k, v) in windmill_headers {
|
||||
let k = HeaderName::from_str(k.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid header name {k}: {err}"))
|
||||
})?;
|
||||
let v = HeaderValue::from_str(v.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid header value {v}: {err}"))
|
||||
})?;
|
||||
headers.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(content_type) = windmill_content_type {
|
||||
let serialized_json_result = result_value
|
||||
.map(|val| val.get().to_owned())
|
||||
.unwrap_or_else(String::new);
|
||||
// if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string.
|
||||
// it falls back to the original serialized JSON if it doesn't work.
|
||||
let serialized_result =
|
||||
serde_json::from_str::<String>(serialized_json_result.as_str())
|
||||
.ok()
|
||||
.unwrap_or(serialized_json_result);
|
||||
headers.insert(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_str(content_type.as_str()).map_err(|err| {
|
||||
Error::InternalErr(format!("Invalid content type {content_type}: {err}"))
|
||||
})?,
|
||||
);
|
||||
return Ok((status_code_or_default, headers, serialized_result).into_response());
|
||||
}
|
||||
if let Some(result_value) = result_value {
|
||||
return Ok((status_code_or_default, headers, Json(result_value)).into_response());
|
||||
} else {
|
||||
Ok((status_code_or_default, headers).into_response())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
|
||||
_ => Ok((
|
||||
if success {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
Json(result),
|
||||
)
|
||||
.into_response()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,12 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub async fn start_kafka_consumers(
|
||||
pub fn start_kafka_consumers(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum KafkaTriggerConfigConnection {}
|
||||
|
||||
@@ -59,8 +59,6 @@ mod auth;
|
||||
mod capture;
|
||||
mod concurrency_groups;
|
||||
mod configs;
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
mod postgres_triggers;
|
||||
mod db;
|
||||
mod drafts;
|
||||
pub mod ee;
|
||||
@@ -75,6 +73,8 @@ mod http_triggers;
|
||||
mod indexer_ee;
|
||||
mod inputs;
|
||||
mod integration;
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
mod postgres_triggers;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod apps_ee;
|
||||
@@ -98,12 +98,12 @@ mod scripts;
|
||||
mod service_logs;
|
||||
mod settings;
|
||||
mod slack_approvals;
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod teams_ee;
|
||||
#[cfg(feature = "smtp")]
|
||||
mod smtp_server_ee;
|
||||
mod static_assets;
|
||||
mod stripe_ee;
|
||||
#[cfg(feature = "enterprise")]
|
||||
mod teams_ee;
|
||||
mod tracing_init;
|
||||
mod triggers;
|
||||
mod users;
|
||||
@@ -296,24 +296,24 @@ pub async fn run_server(
|
||||
#[cfg(feature = "websocket")]
|
||||
{
|
||||
let ws_killpill_rx = rx.resubscribe();
|
||||
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx).await;
|
||||
websocket_triggers::start_websockets(db.clone(), ws_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
let kafka_killpill_rx = rx.resubscribe();
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx).await;
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats"))]
|
||||
{
|
||||
let nats_killpill_rx = rx.resubscribe();
|
||||
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx).await;
|
||||
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx);
|
||||
}
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
{
|
||||
let db_killpill_rx = rx.resubscribe();
|
||||
postgres_triggers::start_database(db.clone(), db_killpill_rx).await;
|
||||
postgres_triggers::start_database(db.clone(), db_killpill_rx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ pub async fn run_server(
|
||||
)
|
||||
.route("/slack", post(slack_approvals::slack_app_callback_handler))
|
||||
.nest("/teams", {
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
teams_ee::teams_service()
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub async fn start_nats_consumers(
|
||||
_db: DB,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum NatsTriggerConfigConnection {}
|
||||
|
||||
@@ -46,6 +46,7 @@ pub struct Database {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub dbname: String,
|
||||
#[serde(default)]
|
||||
pub sslmode: String,
|
||||
pub root_certificate_pem: String,
|
||||
}
|
||||
@@ -251,6 +252,12 @@ pub async fn create_postgres_trigger(
|
||||
Path(w_id): Path<String>,
|
||||
Json(new_postgres_trigger): Json<NewPostgresTrigger>,
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
if *CLOUD_HOSTED {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Postgres triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let NewPostgresTrigger {
|
||||
postgres_resource_path,
|
||||
path,
|
||||
@@ -262,12 +269,6 @@ pub async fn create_postgres_trigger(
|
||||
publication,
|
||||
} = new_postgres_trigger;
|
||||
|
||||
if *CLOUD_HOSTED {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Postgres triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if publication_name.is_none() && publication.is_none() {
|
||||
return Err(error::Error::BadRequest(
|
||||
"Publication data is missing".to_string(),
|
||||
@@ -283,14 +284,14 @@ pub async fn create_postgres_trigger(
|
||||
if create_publication || create_slot {
|
||||
let generate_random_string = move || {
|
||||
let timestamp = Utc::now().timestamp_millis().to_string();
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = rand::rng();
|
||||
let charset = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
let random_part = (0..10)
|
||||
.map(|_| {
|
||||
charset
|
||||
.chars()
|
||||
.nth(rng.gen_range(0..charset.len()))
|
||||
.nth(rng.random_range(0..charset.len()))
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
@@ -110,14 +110,13 @@ async fn run_job(
|
||||
trigger: &PostgresTrigger,
|
||||
) -> anyhow::Result<()> {
|
||||
let args = PushArgsOwned { args: args.unwrap_or_default(), extra };
|
||||
let label_prefix = Some(format!("db-{}-", trigger.path));
|
||||
|
||||
let authed = fetch_api_authed(
|
||||
trigger.edited_by.clone(),
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
db,
|
||||
Some("anonymous".to_string()),
|
||||
Some(format!("postgres-{}", trigger.path)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -134,7 +133,7 @@ async fn run_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -146,7 +145,7 @@ async fn run_job(
|
||||
StripPath(trigger.script_path.to_owned()),
|
||||
run_query,
|
||||
args,
|
||||
label_prefix,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ use crate::{
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use chrono::TimeZone;
|
||||
use futures::{pin_mut, SinkExt, StreamExt};
|
||||
use native_tls::TlsConnector;
|
||||
use pg_escape::{quote_identifier, quote_literal};
|
||||
use rand::seq::SliceRandom;
|
||||
use rust_postgres::{Client, Config, CopyBothDuplex, NoTls, SimpleQueryMessage};
|
||||
use rust_postgres::{config::SslMode, Client, Config, CopyBothDuplex, SimpleQueryMessage};
|
||||
use rust_postgres_native_tls::MakeTlsConnector;
|
||||
use windmill_common::{
|
||||
db::UserDB, utils::report_critical_error, worker::to_raw_value, INSTANCE_NAME,
|
||||
};
|
||||
@@ -60,28 +62,50 @@ impl RowExist for Vec<SimpleQueryMessage> {
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("Error from database: {0}")]
|
||||
Postgres(rust_postgres::Error),
|
||||
Postgres(#[from] rust_postgres::Error),
|
||||
#[error("Error : {0}")]
|
||||
Common(windmill_common::error::Error),
|
||||
Common(#[from] windmill_common::error::Error),
|
||||
#[error("Tls Error: {0}")]
|
||||
Tls(#[from] native_tls::Error),
|
||||
}
|
||||
|
||||
pub struct PostgresSimpleClient(Client);
|
||||
|
||||
impl PostgresSimpleClient {
|
||||
async fn new(database: &Database) -> Result<Self, Error> {
|
||||
let ssl_mode = match database.sslmode.as_ref() {
|
||||
"disable" => SslMode::Disable,
|
||||
"" | "prefer" | "allow" => SslMode::Prefer,
|
||||
"require" => SslMode::Require,
|
||||
"verify-ca" => SslMode::VerifyCa,
|
||||
"verify-full" => SslMode::VerifyFull,
|
||||
ssl_mode => {
|
||||
return Err(Error::Common(windmill_common::error::Error::BadRequest(
|
||||
format!("Invalid ssl mode for postgres: {}, please put a valid ssl_mode among the following avalible ssl mode: ['disable', 'allow', 'prefer', 'verify-ca', 'verify-full']", ssl_mode),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut config = Config::new();
|
||||
config
|
||||
.dbname(&database.dbname)
|
||||
.host(&database.host)
|
||||
.port(database.port)
|
||||
.user(&database.user)
|
||||
.ssl_mode(ssl_mode)
|
||||
.replication_mode(rust_postgres::config::ReplicationMode::Logical);
|
||||
|
||||
if !database.password.is_empty() {
|
||||
config.password(&database.password);
|
||||
}
|
||||
|
||||
let (client, connection) = config.connect(NoTls).await.map_err(Error::Postgres)?;
|
||||
if !database.root_certificate_pem.is_empty() {
|
||||
config.ssl_root_cert(database.root_certificate_pem.as_bytes());
|
||||
}
|
||||
|
||||
let connector = MakeTlsConnector::new(TlsConnector::new()?);
|
||||
|
||||
let (client, connection) = config.connect(connector).await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
tracing::debug!("{:#?}", e);
|
||||
@@ -111,8 +135,7 @@ impl PostgresSimpleClient {
|
||||
Ok((
|
||||
self.0
|
||||
.copy_both_simple::<bytes::Bytes>(query.as_str())
|
||||
.await
|
||||
.map_err(Error::Postgres)?,
|
||||
.await?,
|
||||
LogicalReplicationSettings::new(false),
|
||||
))
|
||||
}
|
||||
@@ -250,8 +273,7 @@ async fn listen_to_transactions(
|
||||
&db,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(Error::Common)?;
|
||||
.await?;
|
||||
|
||||
let database = get_database_resource(
|
||||
authed,
|
||||
@@ -260,8 +282,7 @@ async fn listen_to_transactions(
|
||||
&postgres_trigger.postgres_resource_path,
|
||||
&postgres_trigger.workspace_id,
|
||||
)
|
||||
.await
|
||||
.map_err(Error::Common)?;
|
||||
.await?;
|
||||
|
||||
let client = PostgresSimpleClient::new(&database).await?;
|
||||
|
||||
@@ -274,7 +295,6 @@ async fn listen_to_transactions(
|
||||
|
||||
Ok::<_, Error>((logical_replication_stream, logical_replication_settings))
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = killpill_rx.recv() => {
|
||||
@@ -305,7 +325,10 @@ async fn listen_to_transactions(
|
||||
let message = match message {
|
||||
Some(message) => message,
|
||||
None => {
|
||||
tracing::info!("Stream for postgres trigger {} is empty, leaving....", postgres_trigger.path);
|
||||
tracing::error!("Stream for postgres trigger {} closed", postgres_trigger.path);
|
||||
if let None = update_ping(&db, postgres_trigger, Some("Stream closed")).await {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -359,7 +382,7 @@ async fn listen_to_transactions(
|
||||
Some((update.o_id, relations.body_to_json((update.o_id, update.new_tuple)), "update"))
|
||||
}
|
||||
Delete(delete) => {
|
||||
let body = delete.old_tuple.unwrap_or(delete.key_tuple.unwrap());
|
||||
let body = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap());
|
||||
Some((delete.o_id, relations.body_to_json((delete.o_id, body)), "delete"))
|
||||
}
|
||||
};
|
||||
@@ -389,7 +412,8 @@ async fn listen_to_transactions(
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Postgres trigger error while trying to start_logical_replication_streaming: {}", err.to_string())
|
||||
tracing::error!("Postgres trigger error while trying to start logical replication streaming: {}", &err);
|
||||
disable_with_error(&postgres_trigger, &db, err.to_string()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,7 +513,7 @@ async fn listen_to_unlistened_database_events(
|
||||
|
||||
match postgres_triggers {
|
||||
Ok(mut triggers) => {
|
||||
triggers.shuffle(&mut rand::thread_rng());
|
||||
triggers.shuffle(&mut rand::rng());
|
||||
for trigger in triggers {
|
||||
try_to_listen_to_database_transactions(
|
||||
trigger,
|
||||
@@ -505,7 +529,7 @@ async fn listen_to_unlistened_database_events(
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn start_database(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) {
|
||||
pub fn start_database(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) {
|
||||
tokio::spawn(async move {
|
||||
listen_to_unlistened_database_events(&db, &killpill_rx).await;
|
||||
loop {
|
||||
|
||||
@@ -23,7 +23,7 @@ use windmill_common::{
|
||||
error::{self, Error},
|
||||
jobs::JobKind,
|
||||
scripts::ScriptHash,
|
||||
variables::{build_crypt, decrypt_value_with_mc},
|
||||
variables::{build_crypt, decrypt},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -853,7 +853,7 @@ async fn get_slack_token(db: &DB, slack_resource_path: &str, w_id: &str) -> anyh
|
||||
|
||||
if slack_token.is_secret {
|
||||
let mc = build_crypt(&db, w_id).await?;
|
||||
let bot_token = decrypt_value_with_mc(slack_token.value, mc).await?;
|
||||
let bot_token = decrypt(&mc, slack_token.value)?;
|
||||
Ok(bot_token)
|
||||
} else {
|
||||
Ok(slack_token.value)
|
||||
|
||||
@@ -1718,6 +1718,7 @@ pub async fn create_session_token<'c>(
|
||||
if COOKIE_DOMAIN.is_some() {
|
||||
cookie.set_domain(COOKIE_DOMAIN.clone().unwrap());
|
||||
}
|
||||
|
||||
let mut expire: OffsetDateTime = time::OffsetDateTime::now_utc();
|
||||
expire += time::Duration::days(3);
|
||||
cookie.set_expires(expire);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -121,7 +121,8 @@ pub fn workspaced_service() -> Router {
|
||||
"/critical_alerts/acknowledge_all",
|
||||
post(acknowledge_all_critical_alerts),
|
||||
)
|
||||
.route("/critical_alerts/mute", post(mute_critical_alerts));
|
||||
.route("/critical_alerts/mute", post(mute_critical_alerts))
|
||||
.route("/operator_settings", post(update_operator_settings));
|
||||
|
||||
#[cfg(feature = "stripe")]
|
||||
{
|
||||
@@ -188,6 +189,7 @@ pub struct WorkspaceSettings {
|
||||
pub default_scripts: Option<serde_json::Value>,
|
||||
pub mute_critical_alerts: Option<bool>,
|
||||
pub color: Option<String>,
|
||||
pub operator_settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -286,6 +288,7 @@ struct UserWorkspace {
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub color: Option<String>,
|
||||
pub operator_settings: Option<Option<serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -1366,7 +1369,8 @@ async fn user_workspaces(
|
||||
let mut tx = db.begin().await?;
|
||||
let workspaces = sqlx::query_as!(
|
||||
UserWorkspace,
|
||||
"SELECT workspace.id, workspace.name, usr.username, workspace_settings.color
|
||||
"SELECT workspace.id, workspace.name, usr.username, workspace_settings.color,
|
||||
CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings
|
||||
FROM workspace
|
||||
JOIN usr ON usr.workspace_id = workspace.id
|
||||
JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id
|
||||
@@ -2135,3 +2139,41 @@ async fn mute_critical_alerts(
|
||||
pub async fn mute_critical_alerts() -> Error {
|
||||
Error::NotFound("Critical Alerts require EE".to_string())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct ChangeOperatorSettings {
|
||||
runs: bool,
|
||||
schedules: bool,
|
||||
resources: bool,
|
||||
variables: bool,
|
||||
triggers: bool,
|
||||
audit_logs: bool,
|
||||
groups: bool,
|
||||
folders: bool,
|
||||
workers: bool,
|
||||
}
|
||||
|
||||
async fn update_operator_settings(
|
||||
authed: ApiAuthed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(settings): Json<ChangeOperatorSettings>,
|
||||
) -> Result<String> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let settings_json = serde_json::json!(settings);
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2",
|
||||
settings_json,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok("Operator settings updated successfully".to_string())
|
||||
}
|
||||
|
||||
@@ -529,7 +529,7 @@ pub mod script {
|
||||
lock AS \"lock: String\", \
|
||||
language AS \"language: Option<ScriptLang>\", \
|
||||
envs AS \"envs: Vec<String>\", \
|
||||
codebase AS \"codebase: String\" \
|
||||
codebase LIKE '%.tar' as use_tar \
|
||||
FROM script WHERE hash = $1 LIMIT 1",
|
||||
hash.0
|
||||
)
|
||||
@@ -543,7 +543,16 @@ pub mod script {
|
||||
meta: Some(ScriptMetadata {
|
||||
language: r.language,
|
||||
envs: r.envs,
|
||||
codebase: r.codebase,
|
||||
codebase: if let Some(use_tar) = r.use_tar {
|
||||
let sh = hash.to_string();
|
||||
if use_tar {
|
||||
Some(format!("{sh}.tar"))
|
||||
} else {
|
||||
Some(sh)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -161,8 +161,8 @@ impl Retry {
|
||||
if let Some(random_factor) = exponential.random_factor {
|
||||
if random_factor > 0 {
|
||||
let random_component =
|
||||
rand::thread_rng().gen_range(0..(std::cmp::min(random_factor, 100) as u16));
|
||||
secs = match rand::thread_rng().gen_bool(1.0 / 2.0) {
|
||||
rand::rng().random_range(0..(std::cmp::min(random_factor, 100) as u16));
|
||||
secs = match rand::rng().random_bool(1.0 / 2.0) {
|
||||
true => secs.saturating_add(secs * random_component / 100),
|
||||
false => secs.saturating_sub(secs * random_component / 100),
|
||||
};
|
||||
|
||||
@@ -19,7 +19,8 @@ use git_version::git_version;
|
||||
|
||||
use chrono::Utc;
|
||||
use croner::Cron;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::{rng, Rng};
|
||||
use reqwest::Client;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -205,7 +206,7 @@ pub async fn http_get_from_hub(
|
||||
}
|
||||
|
||||
pub fn rd_string(len: usize) -> String {
|
||||
thread_rng()
|
||||
rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(len)
|
||||
.map(char::from)
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::error;
|
||||
use crate::{worker::WORKER_GROUP, BASE_URL, DB};
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
use magic_crypt::{MagicCrypt256, MagicCryptError, MagicCryptTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SECRET_SALT: Option<String> = std::env::var("SECRET_SALT").ok();
|
||||
@@ -134,7 +133,7 @@ pub async fn get_secret_value_as_admin(
|
||||
let value = variable.value;
|
||||
if !value.is_empty() {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
decrypt_value_with_mc(value, mc).await?
|
||||
decrypt(&mc, value)?
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
@@ -145,16 +144,6 @@ pub async fn get_secret_value_as_admin(
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub async fn decrypt_value_with_mc(value: String, mc: MagicCrypt256) -> Result<String> {
|
||||
mc.decrypt_base64_to_string(value).map_err(|e| match e {
|
||||
MagicCryptError::DecryptError(_) => crate::error::Error::InternalErr(
|
||||
"Could not decrypt value. The value may have been encrypted with a different key."
|
||||
.to_string(),
|
||||
),
|
||||
_ => crate::error::Error::InternalErr(e.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encrypt(mc: &MagicCrypt256, value: &str) -> String {
|
||||
mc.encrypt_str_to_base64(value)
|
||||
}
|
||||
|
||||
@@ -123,6 +123,13 @@ mount {
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{GLOBAL_SITE_PACKAGES}"
|
||||
dst: "{GLOBAL_SITE_PACKAGES}"
|
||||
is_bind: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
{SHARED_MOUNT}
|
||||
|
||||
{SHARED_DEPENDENCIES}
|
||||
|
||||
@@ -18,7 +18,7 @@ use windmill_common::jobs::ENTRYPOINT_OVERRIDE;
|
||||
use windmill_common::s3_helpers::{
|
||||
get_etag_or_empty, LargeFileStorage, ObjectStoreResource, S3Object,
|
||||
};
|
||||
use windmill_common::variables::{build_crypt_with_key_suffix, decrypt_value_with_mc};
|
||||
use windmill_common::variables::{build_crypt_with_key_suffix, decrypt};
|
||||
use windmill_common::worker::{
|
||||
to_raw_value, write_file, CLOUD_HOSTED, ROOT_CACHE_DIR, WORKER_CONFIG,
|
||||
};
|
||||
@@ -63,10 +63,10 @@ pub fn check_executor_binary_exists(
|
||||
) -> Result<(), Error> {
|
||||
if !Path::new(executor_path).exists() {
|
||||
#[cfg(feature = "enterprise")]
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run {language} jobs.", executor_path);
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-ee-full image. Please use the image `ghcr.io/windmill-labs/windmill-ee-full` for your instance in order to run {language} jobs.", executor_path);
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run {language} jobs.", executor_path);
|
||||
let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `ghcr.io/windmill-labs/windmill-full` for your instance in order to run {language} jobs.", executor_path);
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
|
||||
@@ -255,11 +255,9 @@ pub async fn transform_json_value(
|
||||
let encrypted = y.strip_prefix("$encrypted:").unwrap();
|
||||
let mc =
|
||||
build_crypt_with_key_suffix(&db, &job.workspace_id, &job.id.to_string()).await?;
|
||||
decrypt_value_with_mc(encrypted.to_string(), mc)
|
||||
.await
|
||||
.and_then(|x| {
|
||||
serde_json::from_str(&x).map_err(|e| Error::InternalErr(e.to_string()))
|
||||
})
|
||||
decrypt(&mc, encrypted.to_string()).and_then(|x| {
|
||||
serde_json::from_str(&x).map_err(|e| Error::InternalErr(e.to_string()))
|
||||
})
|
||||
|
||||
// let path = y.strip_prefix("$res:").unwrap();
|
||||
}
|
||||
|
||||
@@ -72,7 +72,10 @@ pub async fn do_mssql(
|
||||
|
||||
let mut config = Config::new();
|
||||
|
||||
config.host(database.host);
|
||||
let host_ref = &database.host;
|
||||
let port_ref = database.port;
|
||||
|
||||
config.host(host_ref.clone());
|
||||
config.database(database.dbname);
|
||||
let use_instance_name = database.instance_name.as_ref().is_some_and(|x| x != "");
|
||||
if use_instance_name {
|
||||
@@ -101,12 +104,26 @@ pub async fn do_mssql(
|
||||
};
|
||||
tcp.set_nodelay(true)?;
|
||||
|
||||
// To be able to use Tokio's tcp, we're using the `compat_write` from
|
||||
// the `TokioAsyncWriteCompatExt` to get a stream compatible with the
|
||||
// traits from the `futures` crate.
|
||||
let mut client = Client::connect(config, tcp.compat_write())
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
// NOTE Azure default behavior with SQL Server is to redirect:
|
||||
// https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-architecture?view=azuresql#connection-policy
|
||||
// https://github.com/prisma/tiberius?tab=readme-ov-file#redirects
|
||||
let mut client = match Client::connect(config.clone(), tcp.compat_write()).await {
|
||||
Ok(client) => {
|
||||
tracing::debug!("Connected to host: {:#?}, port: {:#?}", host_ref, port_ref);
|
||||
client
|
||||
}
|
||||
Err(tiberius::error::Error::Routing { host, port }) => {
|
||||
tracing::debug!("Redirecting to host: {:#?}, port: {:#?}", host, port);
|
||||
config.host(&host);
|
||||
config.port(port);
|
||||
|
||||
let tcp = TcpStream::connect(config.get_addr()).await?;
|
||||
tcp.set_nodelay(true)?;
|
||||
|
||||
Client::connect(config, tcp.compat_write()).await.map_err(to_anyhow)?
|
||||
}
|
||||
Err(e) => return Err(Error::Anyhow(to_anyhow(e))),
|
||||
};
|
||||
|
||||
let sig = parse_mssql_sig(&query)
|
||||
.map_err(|x| Error::ExecutionErr(x.to_string()))?
|
||||
|
||||
@@ -36,36 +36,38 @@ use windmill_common::{
|
||||
use windmill_common::variables::get_secret_value_as_admin;
|
||||
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
use std::env::var;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PYTHON_PATH: String =
|
||||
std::env::var("PYTHON_PATH").unwrap_or_else(|_| "/usr/local/bin/python3".to_string());
|
||||
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());
|
||||
var("UV_PATH").unwrap_or_else(|_| "/usr/local/bin/uv".to_string());
|
||||
|
||||
static ref PY_CONCURRENT_DOWNLOADS: usize =
|
||||
std::env::var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20);
|
||||
var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20);
|
||||
|
||||
static ref FLOCK_PATH: String =
|
||||
std::env::var("FLOCK_PATH").unwrap_or_else(|_| "/usr/bin/flock".to_string());
|
||||
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();
|
||||
|
||||
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 TRUSTED_HOST: Option<String> = var("PY_TRUSTED_HOST").ok().or(var("PIP_TRUSTED_HOST").ok());
|
||||
static ref INDEX_CERT: Option<String> = var("PY_INDEX_CERT").ok().or(var("PIP_INDEX_CERT").ok());
|
||||
static ref NATIVE_CERT: bool = var("PY_NATIVE_CERT").ok().or(var("UV_NATIVE_TLS").ok()).map(|flag| flag == "true").unwrap_or(false);
|
||||
|
||||
pub static ref USE_SYSTEM_PYTHON: bool = std::env::var("USE_SYSTEM_PYTHON")
|
||||
pub static ref USE_SYSTEM_PYTHON: bool = var("USE_SYSTEM_PYTHON")
|
||||
.ok().map(|flag| flag == "true").unwrap_or(false);
|
||||
|
||||
pub static ref USE_PIP_COMPILE: bool = std::env::var("USE_PIP_COMPILE")
|
||||
pub static ref USE_PIP_COMPILE: bool = var("USE_PIP_COMPILE")
|
||||
.ok().map(|flag| flag == "true").unwrap_or(false);
|
||||
|
||||
pub static ref USE_PIP_INSTALL: bool = std::env::var("USE_PIP_INSTALL")
|
||||
pub static ref USE_PIP_INSTALL: bool = var("USE_PIP_INSTALL")
|
||||
.ok().map(|flag| flag == "true").unwrap_or(false);
|
||||
|
||||
static ref RELATIVE_IMPORT_REGEX: Regex = Regex::new(r#"(import|from)\s(((u|f)\.)|\.)"#).unwrap();
|
||||
|
||||
static ref EPHEMERAL_TOKEN_CMD: Option<String> = std::env::var("EPHEMERAL_TOKEN_CMD").ok();
|
||||
static ref EPHEMERAL_TOKEN_CMD: Option<String> = var("EPHEMERAL_TOKEN_CMD").ok();
|
||||
}
|
||||
|
||||
const NSJAIL_CONFIG_DOWNLOAD_PY_CONTENT: &str = include_str!("../nsjail/download.py.config.proto");
|
||||
@@ -533,10 +535,10 @@ pub async fn uv_pip_compile(
|
||||
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() {
|
||||
if let Some(host) = TRUSTED_HOST.as_ref() {
|
||||
args.extend(["--trusted-host", host]);
|
||||
}
|
||||
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
|
||||
if let Some(cert_path) = INDEX_CERT.as_ref() {
|
||||
args.extend(["--cert", cert_path]);
|
||||
}
|
||||
let pip_args_str = pip_args.join(" ");
|
||||
@@ -622,12 +624,15 @@ pub async fn uv_pip_compile(
|
||||
if let Some(url) = pip_index_url.as_ref() {
|
||||
args.extend(["--index-url", url]);
|
||||
}
|
||||
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
|
||||
if let Some(host) = TRUSTED_HOST.as_ref() {
|
||||
args.extend(["--trusted-host", host]);
|
||||
}
|
||||
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
|
||||
if let Some(cert_path) = INDEX_CERT.as_ref() {
|
||||
args.extend(["--cert", cert_path]);
|
||||
}
|
||||
if *NATIVE_CERT {
|
||||
args.extend(["--native-tls"]);
|
||||
}
|
||||
tracing::error!("uv args: {:?}", args);
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -898,6 +903,8 @@ pub async fn handle_python_job(
|
||||
tracing::debug!("Finished deps postinstall stage");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if no_uv {
|
||||
append_logs(
|
||||
&job.id,
|
||||
@@ -1046,7 +1053,27 @@ except BaseException as e:
|
||||
|
||||
let client = client.get_authed().await;
|
||||
let mut reserved_variables = get_reserved_variables(job, &client.token, db).await?;
|
||||
let additional_python_paths_folders = additional_python_paths.iter().join(":");
|
||||
|
||||
// Add /tmp/windmill/cache/python_xyz/global-site-packages to PYTHONPATH.
|
||||
// Usefull if certain wheels needs to be preinstalled before execution.
|
||||
let global_site_packages_path = py_version.to_cache_dir() + "/global-site-packages";
|
||||
let additional_python_paths_folders = {
|
||||
let mut paths= additional_python_paths.clone();
|
||||
if std::fs::metadata(&global_site_packages_path).is_ok() {
|
||||
// We want global_site_packages_path to be included in additonal_python_paths_folders, but
|
||||
// we don't want it to be included in global_site_packages_path.
|
||||
// The reason for this is that additional_python_paths_folders is used to fill PYTHONPATH env variable for jailed script
|
||||
// When global_site_packages_path used to place mount point of wheels to the jail config.
|
||||
// Since we handle mount of global_site_packages on our own, we don't want it to be mounted automatically.
|
||||
// We do this because existence of every wheel in cache is mandatory and if it is not there and nsjail expects it, it is a bug.
|
||||
// On the other side global_site_packages is purely optional.
|
||||
// NOTE: This behaviour can be changed in future, so verification of wheels can be offloaded from nsjail to windmill
|
||||
paths.insert(0, global_site_packages_path.clone());
|
||||
// ^^^^^^^^
|
||||
// We also want this be priorotized, that's why we insert it to the beginning
|
||||
}
|
||||
paths.iter().join(":")
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let additional_python_paths_folders = additional_python_paths_folders.replace(":", ";");
|
||||
@@ -1076,6 +1103,7 @@ mount {{
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount)
|
||||
.replace("{SHARED_DEPENDENCIES}", shared_deps.as_str())
|
||||
.replace("{GLOBAL_SITE_PACKAGES}", &global_site_packages_path)
|
||||
.replace("{MAIN}", format!("{dirs}/{last}").as_str())
|
||||
.replace(
|
||||
"{ADDITIONAL_PYTHON_PATHS}",
|
||||
@@ -1573,12 +1601,15 @@ async fn spawn_uv_install(
|
||||
if let Some(url) = pip_index_url.as_ref() {
|
||||
vars.push(("INDEX_URL", url));
|
||||
}
|
||||
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
|
||||
vars.push(("PIP_INDEX_CERT", cert_path));
|
||||
if let Some(cert_path) = INDEX_CERT.as_ref() {
|
||||
vars.push(("SSL_CERT_FILE", cert_path));
|
||||
}
|
||||
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
|
||||
if let Some(host) = TRUSTED_HOST.as_ref() {
|
||||
vars.push(("TRUSTED_HOST", host));
|
||||
}
|
||||
if *NATIVE_CERT {
|
||||
vars.push(("UV_NATIVE_TLS", "true"));
|
||||
}
|
||||
let _owner;
|
||||
if let Some(py_path) = py_path.as_ref() {
|
||||
_owner = format!(
|
||||
@@ -1675,18 +1706,26 @@ async fn spawn_uv_install(
|
||||
});
|
||||
}
|
||||
|
||||
let mut envs = vec![("PATH", PATH_ENV.as_str())];
|
||||
envs.push(("HOME", HOME_ENV.as_str()));
|
||||
|
||||
if let Some(url) = pip_index_url.as_ref() {
|
||||
command_args.extend(["--index-url", url]);
|
||||
}
|
||||
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
|
||||
command_args.extend(["--cert", cert_path]);
|
||||
}
|
||||
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
|
||||
if let Some(host) = TRUSTED_HOST.as_ref() {
|
||||
command_args.extend(["--trusted-host", &host]);
|
||||
}
|
||||
|
||||
let mut envs = vec![("PATH", PATH_ENV.as_str())];
|
||||
envs.push(("HOME", HOME_ENV.as_str()));
|
||||
if *NATIVE_CERT {
|
||||
command_args.extend(["--native-tls"]);
|
||||
}
|
||||
// TODO:
|
||||
// Track https://github.com/astral-sh/uv/issues/6715
|
||||
if let Some(cert_path) = INDEX_CERT.as_ref() {
|
||||
// Once merged --cert can be used instead
|
||||
//
|
||||
// command_args.extend(["--cert", cert_path]);
|
||||
envs.push(("SSL_CERT_FILE", cert_path));
|
||||
}
|
||||
|
||||
tracing::debug!("uv pip install command: {:?}", command_args);
|
||||
|
||||
@@ -2159,6 +2198,18 @@ pub async fn handle_python_reqs(
|
||||
db
|
||||
).await;
|
||||
pids.lock().await.get_mut(i).and_then(|e| e.take());
|
||||
|
||||
// Create a file to indicate that installation was successfull
|
||||
let valid_path = venv_p.clone() + "/.valid.windmill";
|
||||
// This is atomic operation, meaning, that it either completes and wheel is valid,
|
||||
// or it does not and wheel is invalid and will be reinstalled next run
|
||||
if let Err(e) = File::create(&valid_path).await{
|
||||
tracing::error!(
|
||||
workspace_id = %w_id,
|
||||
job_id = %job_id,
|
||||
"Failed to create {}!\n{e}\n
|
||||
This file needed for python jobs to function", valid_path)
|
||||
};
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1095,7 +1095,7 @@ pub async fn run_worker(
|
||||
#[cfg(feature = "benchmark")]
|
||||
let mut infos = BenchmarkInfo::new();
|
||||
|
||||
let vacuum_shift = rand::thread_rng().gen_range(0..VACUUM_PERIOD);
|
||||
let vacuum_shift = rand::rng().random_range(0..VACUUM_PERIOD);
|
||||
|
||||
IS_READY.store(true, Ordering::Relaxed);
|
||||
tracing::info!(
|
||||
|
||||
@@ -62,6 +62,8 @@ use windmill_queue::{
|
||||
|
||||
type DB = sqlx::Pool<sqlx::Postgres>;
|
||||
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_queue::{canceled_job_to_result, push};
|
||||
|
||||
// #[instrument(level = "trace", skip_all)]
|
||||
@@ -1894,6 +1896,12 @@ async fn push_next_flow_job(
|
||||
))
|
||||
&& suspend.continue_on_disapprove_timeout.unwrap_or(false);
|
||||
|
||||
let audit_author = AuditAuthor {
|
||||
username: flow_job.permissioned_as.trim_start_matches("u/").to_string(),
|
||||
email: flow_job.email.clone(),
|
||||
username_override: None,
|
||||
};
|
||||
|
||||
if can_be_resumed || disapproved_or_timeout_but_continue {
|
||||
if disapproved_or_timeout_but_continue {
|
||||
let js = if let Some(disapproved) = is_disapproved.as_ref() {
|
||||
@@ -1903,7 +1911,18 @@ async fn push_next_flow_job(
|
||||
};
|
||||
|
||||
resume_messages.push(to_raw_value(&js));
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&audit_author,
|
||||
"jobs.suspend_resume",
|
||||
ActionKind::Update,
|
||||
&flow_job.workspace_id,
|
||||
Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval but can continue".to_string()}).to_string()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2)
|
||||
@@ -1971,6 +1990,18 @@ async fn push_next_flow_job(
|
||||
|
||||
/* cancelled or we're WaitingForEvents but we don't have enough messages (timed out) */
|
||||
} else {
|
||||
if is_disapproved.is_none() {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&audit_author,
|
||||
"jobs.suspend_resume",
|
||||
ActionKind::Update,
|
||||
&flow_job.workspace_id,
|
||||
Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval and is cancelled".to_string()}).to_string()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
let (logs, error_name) = if let Some(disapprover) = is_disapproved {
|
||||
|
||||
+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.448.1";
|
||||
export const VERSION = "v1.454.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.448.1";
|
||||
export const VERSION = "1.454.1";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
+44
-44
@@ -49,7 +49,7 @@ import { FlowFile, replaceInlineScripts } from "./flow.ts";
|
||||
import { getIsWin } from "./main.ts";
|
||||
import { FlowValue } from "./gen/types.gen.ts";
|
||||
|
||||
export async function generateAllMetadata() {}
|
||||
export async function generateAllMetadata() { }
|
||||
|
||||
function findClosestRawReqs(
|
||||
lang: "bun" | "python3" | "php" | undefined,
|
||||
@@ -104,7 +104,8 @@ export async function generateFlowLockInternal(
|
||||
folder: string,
|
||||
dryRun: boolean,
|
||||
workspace: Workspace,
|
||||
justUpdateMetadataLock?: boolean
|
||||
justUpdateMetadataLock?: boolean,
|
||||
noStaleMessage?: boolean
|
||||
): Promise<string | undefined> {
|
||||
if (folder.endsWith(SEP)) {
|
||||
folder = folder.substring(0, folder.length - 1);
|
||||
@@ -112,7 +113,7 @@ export async function generateFlowLockInternal(
|
||||
const remote_path = folder
|
||||
.replaceAll(SEP, "/")
|
||||
.substring(0, folder.length - ".flow".length);
|
||||
if (!justUpdateMetadataLock) {
|
||||
if (!justUpdateMetadataLock && !noStaleMessage) {
|
||||
log.info(`Generating lock for flow ${folder} at ${remote_path}`);
|
||||
}
|
||||
|
||||
@@ -120,9 +121,11 @@ export async function generateFlowLockInternal(
|
||||
|
||||
const conf = await readLockfile();
|
||||
if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) {
|
||||
log.info(
|
||||
colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`)
|
||||
);
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`)
|
||||
);
|
||||
}
|
||||
return;
|
||||
} else if (dryRun) {
|
||||
return remote_path;
|
||||
@@ -222,12 +225,7 @@ export async function generateScriptMetadataInternal(
|
||||
|
||||
// read script content
|
||||
const scriptContent = await Deno.readTextFile(scriptPath);
|
||||
let metadataContent = await Deno.readTextFile(metadataWithType.path);
|
||||
const c = findCodebase(scriptPath, codebases);
|
||||
|
||||
if (c) {
|
||||
metadataContent += c.digest ?? "";
|
||||
}
|
||||
const metadataContent = await Deno.readTextFile(metadataWithType.path);
|
||||
|
||||
let hash = await generateScriptHash(rawReqs, scriptContent, metadataContent);
|
||||
|
||||
@@ -261,7 +259,9 @@ export async function generateScriptMetadataInternal(
|
||||
}
|
||||
|
||||
if (!opts.schemaOnly && !justUpdateMetadataLock) {
|
||||
if (!c) {
|
||||
const hasCodebase = findCodebase(scriptPath, codebases) != undefined;
|
||||
|
||||
if (!hasCodebase) {
|
||||
await updateScriptLock(
|
||||
workspace,
|
||||
scriptContent,
|
||||
@@ -270,16 +270,13 @@ export async function generateScriptMetadataInternal(
|
||||
metadataParsedContent,
|
||||
rawReqs
|
||||
);
|
||||
metadataParsedContent.codebase = undefined;
|
||||
} else {
|
||||
metadataParsedContent.codebase = c.digest;
|
||||
metadataParsedContent.lock = "";
|
||||
metadataParsedContent.lock = '';
|
||||
}
|
||||
} else {
|
||||
metadataParsedContent.lock =
|
||||
"!inline " + remotePath.replaceAll(SEP, "/") + ".script.lock";
|
||||
}
|
||||
|
||||
let metaPath = remotePath + ".script.yaml";
|
||||
let newMetadataContent = yamlStringify(metadataParsedContent, yamlOptions);
|
||||
if (metadataWithType.isJson) {
|
||||
@@ -287,10 +284,8 @@ export async function generateScriptMetadataInternal(
|
||||
newMetadataContent = JSON.stringify(metadataParsedContent);
|
||||
}
|
||||
|
||||
let metadataContentUsedForHash = newMetadataContent;
|
||||
if (c) {
|
||||
metadataContentUsedForHash += c.digest ?? "";
|
||||
}
|
||||
const metadataContentUsedForHash = newMetadataContent;
|
||||
|
||||
hash = await generateScriptHash(
|
||||
rawReqs,
|
||||
scriptContent,
|
||||
@@ -318,9 +313,9 @@ export async function updateScriptSchema(
|
||||
path
|
||||
);
|
||||
metadataContent.schema = result.schema;
|
||||
if (result.has_preprocessor != null)
|
||||
if (result.has_preprocessor == true)
|
||||
metadataContent.has_preprocessor = result.has_preprocessor;
|
||||
if (result.no_main_func != null)
|
||||
if (result.no_main_func === true)
|
||||
metadataContent.no_main_func = result.no_main_func;
|
||||
}
|
||||
|
||||
@@ -390,7 +385,7 @@ async function updateScriptLock(
|
||||
if (await Deno.stat(lockPath)) {
|
||||
await Deno.remove(lockPath);
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
metadataContent.lock = "";
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -431,7 +426,7 @@ export async function updateFlow(
|
||||
} catch (e) {
|
||||
try {
|
||||
responseText = await rawResponse.text();
|
||||
} catch {}
|
||||
} catch { }
|
||||
throw new Error(
|
||||
`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`
|
||||
);
|
||||
@@ -528,8 +523,11 @@ export function inferSchema(
|
||||
};
|
||||
}
|
||||
|
||||
if (!currentSchema) {
|
||||
currentSchema = {}
|
||||
}
|
||||
currentSchema.required = [];
|
||||
const oldProperties = JSON.parse(JSON.stringify(currentSchema.properties));
|
||||
const oldProperties = JSON.parse(JSON.stringify(currentSchema?.properties ?? {}));
|
||||
currentSchema.properties = {};
|
||||
|
||||
for (const arg of inferedSchema.args) {
|
||||
@@ -576,23 +574,23 @@ export function argSigToJsonSchemaType(
|
||||
| string
|
||||
| { resource: string | null }
|
||||
| {
|
||||
list:
|
||||
| (string | { object: { key: string; typ: any }[] })
|
||||
| { str: any }
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| null;
|
||||
}
|
||||
list:
|
||||
| (string | { object: { key: string; typ: any }[] })
|
||||
| { str: any }
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| null;
|
||||
}
|
||||
| { dynselect: string }
|
||||
| { str: string[] | null }
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| {
|
||||
oneof: [
|
||||
{
|
||||
label: string;
|
||||
properties: { key: string; typ: any }[];
|
||||
}
|
||||
];
|
||||
},
|
||||
oneof: [
|
||||
{
|
||||
label: string;
|
||||
properties: { key: string; typ: any }[];
|
||||
}
|
||||
];
|
||||
},
|
||||
oldS: SchemaProperty
|
||||
): void {
|
||||
const newS: SchemaProperty = { type: "" };
|
||||
@@ -795,10 +793,10 @@ export async function parseMetadataFile(
|
||||
scriptPath: string,
|
||||
generateMetadataIfMissing:
|
||||
| (GlobalOptions & {
|
||||
path: string;
|
||||
workspaceRemote: Workspace;
|
||||
schemaOnly?: boolean;
|
||||
})
|
||||
path: string;
|
||||
workspaceRemote: Workspace;
|
||||
schemaOnly?: boolean;
|
||||
})
|
||||
| undefined,
|
||||
globalDeps: GlobalDeps,
|
||||
codebases: SyncCodebase[]
|
||||
@@ -860,7 +858,9 @@ export async function parseMetadataFile(
|
||||
scriptInitialMetadata = (await yamlParseFile(
|
||||
metadataFilePath
|
||||
)) as ScriptMetadata;
|
||||
replaceLock(scriptInitialMetadata);
|
||||
if (!generateMetadataIfMissing.schemaOnly) {
|
||||
replaceLock(scriptInitialMetadata);
|
||||
}
|
||||
} catch (e) {
|
||||
log.info(
|
||||
colors.yellow(
|
||||
|
||||
+53
-42
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { GlobalOptions, showDiff } from "./types.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
@@ -24,7 +24,6 @@ import { Workspace } from "./workspace.ts";
|
||||
import {
|
||||
generateScriptMetadataInternal,
|
||||
parseMetadataFile,
|
||||
updateScriptSchema,
|
||||
} from "./metadata.ts";
|
||||
import {
|
||||
ScriptLanguage,
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
elementsToMap,
|
||||
findCodebase,
|
||||
readDirRecursiveWithIgnore,
|
||||
Skips,
|
||||
yamlOptions,
|
||||
} from "./sync.ts";
|
||||
import { ignoreF } from "./sync.ts";
|
||||
@@ -119,7 +119,7 @@ export async function findResourceFile(path: string) {
|
||||
if (validCandidates.length > 1) {
|
||||
throw new Error(
|
||||
"Found two resource files for the same resource" +
|
||||
validCandidates.join(", ")
|
||||
validCandidates.join(", ")
|
||||
);
|
||||
}
|
||||
if (validCandidates.length < 1) {
|
||||
@@ -162,7 +162,7 @@ export async function handleFile(
|
||||
workspace: Workspace,
|
||||
alreadySynced: string[],
|
||||
message: string | undefined,
|
||||
opts: (GlobalOptions & { defaultTs?: "bun" | "deno" }) | undefined,
|
||||
opts: (GlobalOptions & { defaultTs?: "bun" | "deno" } & Skips) | undefined,
|
||||
globalDeps: GlobalDeps,
|
||||
codebases: SyncCodebase[]
|
||||
): Promise<boolean> {
|
||||
@@ -238,21 +238,22 @@ export async function handleFile(
|
||||
}
|
||||
log.info(`Finished building the bundle for ${path}`);
|
||||
}
|
||||
const typed = (
|
||||
await parseMetadataFile(
|
||||
remotePath,
|
||||
opts
|
||||
? {
|
||||
let typed =
|
||||
opts?.skipScriptsMetadata ? undefined :
|
||||
(await parseMetadataFile(
|
||||
remotePath,
|
||||
opts
|
||||
? {
|
||||
...opts,
|
||||
path,
|
||||
workspaceRemote: workspace,
|
||||
schemaOnly: codebase ? true : undefined,
|
||||
}
|
||||
: undefined,
|
||||
globalDeps,
|
||||
codebases
|
||||
)
|
||||
)?.payload;
|
||||
: undefined,
|
||||
globalDeps,
|
||||
codebases
|
||||
)
|
||||
)?.payload;
|
||||
|
||||
const workspaceId = workspace.workspaceId;
|
||||
|
||||
@@ -268,20 +269,29 @@ export async function handleFile(
|
||||
}
|
||||
const content = await Deno.readTextFile(path);
|
||||
|
||||
if (codebase) {
|
||||
const typedBefore = JSON.parse(JSON.stringify(typed.schema));
|
||||
await updateScriptSchema(content, language, typed, path);
|
||||
if (typedBefore != typed.schema) {
|
||||
log.info(`Updated metadata for bundle ${path}`);
|
||||
showDiff(
|
||||
yamlStringify(typedBefore, yamlOptions),
|
||||
yamlStringify(typed.schema, yamlOptions)
|
||||
);
|
||||
await Deno.writeTextFile(
|
||||
remotePath + ".script.yaml",
|
||||
yamlStringify(typed as Record<string, any>, yamlOptions)
|
||||
);
|
||||
}
|
||||
if (opts?.skipScriptsMetadata) {
|
||||
// if (codebase) {
|
||||
// const typedBefore = JSON.parse(JSON.stringify(typed.schema));
|
||||
// await updateScriptSchema(content, language, typed, path);
|
||||
// if (typedBefore != typed.schema) {
|
||||
// log.info(`Updated metadata for bundle ${path}`);
|
||||
// showDiff(
|
||||
// yamlStringify(typedBefore, yamlOptions),
|
||||
// yamlStringify(typed.schema, yamlOptions)
|
||||
// );
|
||||
// await Deno.writeTextFile(
|
||||
// remotePath + ".script.yaml",
|
||||
// yamlStringify(typed as Record<string, any>, yamlOptions)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
typed = structuredClone(remote);
|
||||
// }
|
||||
}
|
||||
|
||||
if (typed && codebase) {
|
||||
typed.codebase = codebase.digest;
|
||||
}
|
||||
|
||||
const requestBodyCommon: NewScript = {
|
||||
@@ -312,6 +322,8 @@ export async function handleFile(
|
||||
on_behalf_of_email: typed?.on_behalf_of_email,
|
||||
};
|
||||
|
||||
// log.info(JSON.stringify(requestBodyCommon, null, 2))
|
||||
// log.info(JSON.stringify(opts, null, 2))
|
||||
if (remote) {
|
||||
if (content === remote.content) {
|
||||
if (
|
||||
@@ -327,19 +339,19 @@ export async function handleFile(
|
||||
deepEqual(typed.schema, remote.schema) &&
|
||||
typed.tag == remote.tag &&
|
||||
(typed.ws_error_handler_muted ?? false) ==
|
||||
remote.ws_error_handler_muted &&
|
||||
remote.ws_error_handler_muted &&
|
||||
typed.dedicated_worker == remote.dedicated_worker &&
|
||||
typed.cache_ttl == remote.cache_ttl &&
|
||||
typed.concurrency_time_window_s ==
|
||||
remote.concurrency_time_window_s &&
|
||||
remote.concurrency_time_window_s &&
|
||||
typed.concurrent_limit == remote.concurrent_limit &&
|
||||
Boolean(typed.restart_unless_cancelled) ==
|
||||
Boolean(remote.restart_unless_cancelled) &&
|
||||
Boolean(remote.restart_unless_cancelled) &&
|
||||
Boolean(typed.visible_to_runner_only) ==
|
||||
Boolean(remote.visible_to_runner_only) &&
|
||||
Boolean(remote.visible_to_runner_only) &&
|
||||
Boolean(typed.no_main_func) == Boolean(remote.no_main_func) &&
|
||||
Boolean(typed.has_preprocessor) ==
|
||||
Boolean(remote.has_preprocessor) &&
|
||||
Boolean(remote.has_preprocessor) &&
|
||||
typed.priority == Boolean(remote.priority) &&
|
||||
typed.timeout == remote.timeout &&
|
||||
//@ts-ignore
|
||||
@@ -412,10 +424,9 @@ async function createScript(
|
||||
workspace: workspaceId,
|
||||
requestBody: body,
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
throw Error(
|
||||
`Script creation for ${body.path} with parent ${
|
||||
body.parent_hash
|
||||
`Script creation for ${body.path} with parent ${body.parent_hash
|
||||
} was not successful: ${e.body ?? e.message}`
|
||||
);
|
||||
}
|
||||
@@ -441,8 +452,7 @@ async function createScript(
|
||||
});
|
||||
if (req.status != 201) {
|
||||
throw Error(
|
||||
`Script snapshot creation was not successful: ${req.status} - ${
|
||||
req.statusText
|
||||
`Script snapshot creation was not successful: ${req.status} - ${req.statusText
|
||||
} - ${await req.text()}`
|
||||
);
|
||||
}
|
||||
@@ -453,8 +463,8 @@ export async function findContentFile(filePath: string) {
|
||||
const candidates = filePath.endsWith("script.json")
|
||||
? exts.map((x) => filePath.replace(".script.json", x))
|
||||
: filePath.endsWith("script.lock")
|
||||
? exts.map((x) => filePath.replace(".script.lock", x))
|
||||
: exts.map((x) => filePath.replace(".script.yaml", x));
|
||||
? exts.map((x) => filePath.replace(".script.lock", x))
|
||||
: exts.map((x) => filePath.replace(".script.yaml", x));
|
||||
|
||||
const validCandidates = (
|
||||
await Promise.all(
|
||||
@@ -473,7 +483,7 @@ export async function findContentFile(filePath: string) {
|
||||
if (validCandidates.length > 1) {
|
||||
throw new Error(
|
||||
"No content path given and more than one candidate found: " +
|
||||
validCandidates.join(", ")
|
||||
validCandidates.join(", ")
|
||||
);
|
||||
}
|
||||
if (validCandidates.length < 1) {
|
||||
@@ -858,6 +868,7 @@ async function generateMetadata(
|
||||
} & SyncOptions,
|
||||
scriptPath: string | undefined
|
||||
) {
|
||||
log.info("This command only works for workspace scripts, for flows inline scripts use `wmill flow generate-locks`");
|
||||
if (scriptPath == "") {
|
||||
scriptPath = undefined;
|
||||
}
|
||||
@@ -976,7 +987,7 @@ const command = new Command()
|
||||
.action(bootstrap as any)
|
||||
.command(
|
||||
"generate-metadata",
|
||||
"re-generate the metadata file updating the lock and the script schema"
|
||||
"re-generate the metadata file updating the lock and the script schema (for flows, use `wmill flow generate-locks`)"
|
||||
)
|
||||
.arguments("[script:file]")
|
||||
.option("--yes", "Skip confirmation prompt")
|
||||
|
||||
+228
-120
@@ -101,8 +101,9 @@ async function addCodebaseDigestIfRelevant(
|
||||
return content;
|
||||
}
|
||||
let isTs = true;
|
||||
const replacedPath = path.replace(".script.yaml", ".ts");
|
||||
try {
|
||||
await Deno.stat(path.replace(".script.yaml", ".ts"));
|
||||
await Deno.stat(replacedPath);
|
||||
} catch {
|
||||
isTs = false;
|
||||
}
|
||||
@@ -110,12 +111,12 @@ async function addCodebaseDigestIfRelevant(
|
||||
return content;
|
||||
}
|
||||
if (isTs) {
|
||||
const c = findCodebase(path, codebases);
|
||||
const c = findCodebase(replacedPath, codebases);
|
||||
if (c) {
|
||||
const parsed: any = yamlParseContent(path, content);
|
||||
if (parsed && typeof parsed == "object") {
|
||||
parsed["codebase"] = c.digest;
|
||||
parsed["lock"] = undefined;
|
||||
parsed["lock"] = '';
|
||||
return yamlStringify(parsed, yamlOptions);
|
||||
} else {
|
||||
throw Error(
|
||||
@@ -158,9 +159,8 @@ export async function FSFSElement(
|
||||
// },
|
||||
async getContentText(): Promise<string> {
|
||||
const content = await Deno.readTextFile(localP);
|
||||
|
||||
const r = await addCodebaseDigestIfRelevant(localP, content, codebases);
|
||||
// console.log(r);
|
||||
const itemPath = localP.substring(p.length + 1)
|
||||
const r = await addCodebaseDigestIfRelevant(itemPath, content, codebases);
|
||||
return r;
|
||||
},
|
||||
};
|
||||
@@ -352,12 +352,12 @@ function ZipFSElement(
|
||||
)
|
||||
? "flow"
|
||||
: p.endsWith("app.json")
|
||||
? "app"
|
||||
: p.endsWith("script.json")
|
||||
? "script"
|
||||
: p.endsWith("resource.json")
|
||||
? "resource"
|
||||
: "other";
|
||||
? "app"
|
||||
: p.endsWith("script.json")
|
||||
? "script"
|
||||
: p.endsWith("resource.json")
|
||||
? "resource"
|
||||
: "other";
|
||||
|
||||
const isJson = p.endsWith(".json");
|
||||
|
||||
@@ -387,7 +387,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, s.path),
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return s.content;
|
||||
@@ -398,7 +398,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, "flow.yaml"),
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return yamlStringify(flow, yamlOptions);
|
||||
@@ -414,7 +414,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, s.path),
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return s.content;
|
||||
@@ -425,7 +425,7 @@ function ZipFSElement(
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(finalPath, "app.yaml"),
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return yamlStringify(app, yamlOptions);
|
||||
@@ -490,7 +490,7 @@ function ZipFSElement(
|
||||
r.push({
|
||||
isDirectory: false,
|
||||
path: removeSuffix(finalPath, ".json") + ".lock",
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return lock;
|
||||
@@ -513,7 +513,7 @@ function ZipFSElement(
|
||||
removeSuffix(finalPath, ".resource.json") +
|
||||
".resource.file." +
|
||||
formatExtension,
|
||||
async *getChildren() {},
|
||||
async *getChildren() { },
|
||||
// deno-lint-ignore require-await
|
||||
async getContentText() {
|
||||
return fileContent;
|
||||
@@ -574,19 +574,19 @@ export async function* readDirRecursiveWithIgnore(
|
||||
// getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}[] = [
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
// getContentBytes(): Promise<Uint8Array> {
|
||||
// throw undefined;
|
||||
// },
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
// getContentBytes(): Promise<Uint8Array> {
|
||||
// throw undefined;
|
||||
// },
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const e = stack.pop()!;
|
||||
@@ -606,7 +606,7 @@ export async function* readDirRecursiveWithIgnore(
|
||||
|
||||
type Added = { name: "added"; path: string; content: string };
|
||||
type Deleted = { name: "deleted"; path: string };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string; codebase?: string };
|
||||
|
||||
type Change = Added | Deleted | Edit;
|
||||
|
||||
@@ -649,7 +649,7 @@ export async function elementsToMap(
|
||||
"js",
|
||||
"lock",
|
||||
"rs",
|
||||
"cs",
|
||||
"cs",
|
||||
"yml",
|
||||
].includes(path.split(".").pop() ?? "") &&
|
||||
!isFileResource(path)
|
||||
@@ -677,10 +677,11 @@ export async function elementsToMap(
|
||||
return map;
|
||||
}
|
||||
|
||||
interface Skips {
|
||||
export interface Skips {
|
||||
skipVariables?: boolean | undefined;
|
||||
skipResources?: boolean | undefined;
|
||||
skipSecrets?: boolean | undefined;
|
||||
skipScriptsMetadata?: boolean | undefined;
|
||||
includeSchedules?: boolean | undefined;
|
||||
includeUsers?: boolean | undefined;
|
||||
includeGroups?: boolean | undefined;
|
||||
@@ -694,13 +695,14 @@ async function compareDynFSElement(
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
json: boolean,
|
||||
skips: Skips,
|
||||
ignoreMetadataDeletion: boolean
|
||||
ignoreMetadataDeletion: boolean,
|
||||
codebases: SyncCodebase[]
|
||||
): Promise<Change[]> {
|
||||
const [m1, m2] = els2
|
||||
? await Promise.all([
|
||||
elementsToMap(els1, ignore, json, skips),
|
||||
elementsToMap(els2, ignore, json, skips),
|
||||
])
|
||||
elementsToMap(els1, ignore, json, skips),
|
||||
elementsToMap(els2, ignore, json, skips),
|
||||
])
|
||||
: [await elementsToMap(els1, ignore, json, skips), {}];
|
||||
|
||||
const changes: Change[] = [];
|
||||
@@ -734,25 +736,81 @@ async function compareDynFSElement(
|
||||
return yamlParseContent(k, v);
|
||||
}
|
||||
}
|
||||
for (const [k, v] of Object.entries(m1)) {
|
||||
const codebaseChanges: Record<string, string> = {};
|
||||
for (let [k, v] of Object.entries(m1)) {
|
||||
const isScriptMetadata = k.endsWith(".script.yaml") || k.endsWith(".script.json");
|
||||
const skipMetadata = skips.skipScriptsMetadata && isScriptMetadata;
|
||||
|
||||
if (m2[k] === undefined) {
|
||||
if (skipMetadata) {
|
||||
continue;
|
||||
}
|
||||
changes.push({ name: "added", path: k, content: v });
|
||||
} else if (
|
||||
m2[k] != v &&
|
||||
(!k.endsWith(".json") || !deepEqual(JSON.parse(v), JSON.parse(m2[k]))) &&
|
||||
(!k.endsWith(".yaml") || !deepEqual(parseYaml(k, v), parseYaml(k, m2[k])))
|
||||
) {
|
||||
changes.push({ name: "edited", path: k, after: v, before: m2[k] });
|
||||
} else {
|
||||
if (m2[k] == v) {
|
||||
continue;
|
||||
}
|
||||
else if (k.endsWith(".json")) {
|
||||
if (deepEqual(JSON.parse(v), JSON.parse(m2[k]))) {
|
||||
continue;
|
||||
}
|
||||
} else if (k.endsWith(".yaml")) {
|
||||
const before = parseYaml(k, m2[k]);
|
||||
const after = parseYaml(k, v);
|
||||
if (deepEqual(before, after)) {
|
||||
continue;
|
||||
}
|
||||
if (before.codebase != undefined) {
|
||||
delete before.codebase;
|
||||
m2[k] = yamlStringify(before, yamlOptions);
|
||||
}
|
||||
if (after.codebase != undefined) {
|
||||
if (before.codebase != after.codebase) {
|
||||
codebaseChanges[k] = after.codebase;
|
||||
}
|
||||
delete after.codebase;
|
||||
v = yamlStringify(after, yamlOptions);
|
||||
}
|
||||
if (skipMetadata) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
changes.push({ name: "edited", path: k, after: v, before: m2[k], codebase: codebaseChanges[k] });
|
||||
}
|
||||
}
|
||||
|
||||
const remoteCodebase: Record<string, string> = {};
|
||||
for (const [k] of Object.entries(m2)) {
|
||||
if (
|
||||
m1[k] === undefined &&
|
||||
(!ignoreMetadataDeletion ||
|
||||
(!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json")))
|
||||
) {
|
||||
changes.push({ name: "deleted", path: k });
|
||||
if (m1[k] === undefined) {
|
||||
if (!ignoreMetadataDeletion || (!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json"))) {
|
||||
changes.push({ name: "deleted", path: k });
|
||||
} else if (k?.endsWith(".script.yaml")) {
|
||||
let o = parseYaml(k, m2[k]);
|
||||
if (o.codebase != undefined) {
|
||||
remoteCodebase[k] = o.codebase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(remoteCodebase)) {
|
||||
const tsFile = k.replace(".script.yaml", ".ts");
|
||||
if (changes.find(c => c.path == tsFile && (c.name == "edited" || c.name == "deleted"))) {
|
||||
continue;
|
||||
}
|
||||
let c = findCodebase(tsFile, codebases);
|
||||
if (c?.digest != v) {
|
||||
changes.push({ name: "edited", path: tsFile, codebase: v, before: m1[tsFile], after: m2[tsFile] });
|
||||
}
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
const codebase = codebaseChanges[change.path];
|
||||
if (!codebase) continue;
|
||||
|
||||
const tsFile = change.path.replace(".script.yaml", ".ts");
|
||||
if (change.name == "edited" && change.path == tsFile) {
|
||||
change.codebase = codebase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,6 +945,58 @@ export async function ignoreF(wmillconf: {
|
||||
};
|
||||
}
|
||||
|
||||
interface ChangeTracker {
|
||||
scripts: string[];
|
||||
flows: string[];
|
||||
apps: string[];
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-inner-declarations
|
||||
async function addToChangedIfNotExists(p: string, tracker: ChangeTracker) {
|
||||
const isScript = exts.some((e) => p.endsWith(e));
|
||||
if (isScript) {
|
||||
if (p.includes(".flow" + SEP)) {
|
||||
const folder =
|
||||
p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP;
|
||||
if (!tracker.flows.includes(folder)) {
|
||||
tracker.flows.push(folder);
|
||||
}
|
||||
} else if (p.includes(".app" + SEP)) {
|
||||
const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP;
|
||||
if (!tracker.apps.includes(folder)) {
|
||||
tracker.apps.push(folder);
|
||||
}
|
||||
} else {
|
||||
if (!tracker.scripts.includes(p)) {
|
||||
tracker.scripts.push(p);
|
||||
}
|
||||
}
|
||||
} else if (p.endsWith(".script.yaml") || p.endsWith(".script.json")) {
|
||||
try {
|
||||
const contentPath = await findContentFile(p);
|
||||
if (!contentPath) return;
|
||||
if (tracker.scripts.includes(contentPath)) return;
|
||||
tracker.scripts.push(contentPath);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function buildTracker(changes: Change[]) {
|
||||
const tracker: ChangeTracker = {
|
||||
scripts: [],
|
||||
flows: [],
|
||||
apps: [],
|
||||
};
|
||||
for (const change of changes) {
|
||||
if (change.name == "added" || change.name == "edited") {
|
||||
await addToChangedIfNotExists(change.path, tracker);
|
||||
}
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
|
||||
export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
opts = await mergeConfigWithConfigFile(opts);
|
||||
|
||||
@@ -940,7 +1050,8 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
await ignoreF(opts),
|
||||
opts.json ?? false,
|
||||
opts,
|
||||
false
|
||||
false,
|
||||
codebases
|
||||
);
|
||||
|
||||
log.info(
|
||||
@@ -959,41 +1070,8 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
}
|
||||
|
||||
const conflicts = [];
|
||||
const changedScripts: string[] = [];
|
||||
const changedFlows: string[] = [];
|
||||
const changedApps: string[] = [];
|
||||
|
||||
// deno-lint-ignore no-inner-declarations
|
||||
async function addToChangedIfNotExists(p: string) {
|
||||
const isScript = exts.some((e) => p.endsWith(e));
|
||||
if (isScript) {
|
||||
if (p.includes(".flow" + SEP)) {
|
||||
const folder =
|
||||
p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP;
|
||||
if (!changedFlows.includes(folder)) {
|
||||
changedFlows.push(folder);
|
||||
}
|
||||
} else if (p.includes(".app" + SEP)) {
|
||||
const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP;
|
||||
if (!changedApps.includes(folder)) {
|
||||
changedApps.push(folder);
|
||||
}
|
||||
} else {
|
||||
if (!changedScripts.includes(p)) {
|
||||
changedScripts.push(p);
|
||||
}
|
||||
}
|
||||
} else if (p.endsWith(".script.yaml") || p.endsWith(".script.json")) {
|
||||
try {
|
||||
const contentPath = await findContentFile(p);
|
||||
if (!contentPath) return;
|
||||
if (changedScripts.includes(contentPath)) return;
|
||||
changedScripts.push(contentPath);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.info(colors.gray(`Applying changes to files ...`));
|
||||
for await (const change of changes) {
|
||||
@@ -1054,7 +1132,6 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
await Deno.copyFile(target, stateTarget);
|
||||
}
|
||||
await addToChangedIfNotExists(change.path);
|
||||
} else if (change.name === "added") {
|
||||
await ensureDir(path.dirname(target));
|
||||
if (opts.stateful) {
|
||||
@@ -1066,7 +1143,6 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
if (opts.stateful) {
|
||||
await Deno.copyFile(target, stateTarget);
|
||||
}
|
||||
await addToChangedIfNotExists(change.path);
|
||||
} else if (change.name === "deleted") {
|
||||
try {
|
||||
log.info(
|
||||
@@ -1103,7 +1179,9 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
|
||||
const globalDeps = await findGlobalDeps();
|
||||
|
||||
for (const change of changedScripts) {
|
||||
const tracker: ChangeTracker = await buildTracker(changes);
|
||||
|
||||
for (const change of tracker.scripts) {
|
||||
await generateScriptMetadataInternal(
|
||||
change,
|
||||
workspace,
|
||||
@@ -1115,13 +1193,13 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
|
||||
true
|
||||
);
|
||||
}
|
||||
for (const change of changedFlows) {
|
||||
for (const change of tracker.flows) {
|
||||
log.info(`Updating lock for flow ${change}`);
|
||||
await generateFlowLockInternal(change, false, workspace, true);
|
||||
}
|
||||
if (changedApps.length > 0) {
|
||||
if (tracker.apps.length > 0) {
|
||||
log.info(
|
||||
`Apps ${changedApps.join(
|
||||
`Apps ${tracker.apps.join(
|
||||
", "
|
||||
)} scripts were changed but ignoring for now`
|
||||
);
|
||||
@@ -1146,9 +1224,11 @@ function prettyChanges(changes: Change[]) {
|
||||
);
|
||||
} else if (change.name === "edited") {
|
||||
log.info(
|
||||
colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path + (change.codebase ? ` (codebase changed)` : ""))
|
||||
);
|
||||
showDiff(change.before, change.after);
|
||||
if (change.before != change.after) {
|
||||
showDiff(change.before, change.after);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1238,9 +1318,58 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
await ignoreF(opts),
|
||||
opts.json ?? false,
|
||||
opts,
|
||||
true
|
||||
true,
|
||||
codebases
|
||||
);
|
||||
|
||||
|
||||
const globalDeps = await findGlobalDeps();
|
||||
|
||||
const tracker: ChangeTracker = await buildTracker(changes);
|
||||
|
||||
const staleScripts: string[] = [];
|
||||
const staleFlows: string[] = [];
|
||||
for (const change of tracker.scripts) {
|
||||
const stale = await generateScriptMetadataInternal(
|
||||
change,
|
||||
workspace,
|
||||
opts,
|
||||
true,
|
||||
true,
|
||||
globalDeps,
|
||||
codebases,
|
||||
false
|
||||
);
|
||||
if (stale) {
|
||||
staleScripts.push(stale);
|
||||
}
|
||||
}
|
||||
|
||||
if (staleScripts.length > 0) {
|
||||
log.info("")
|
||||
log.warn("Stale scripts metadata found, you may want to update them using 'wmill script generate-metadata' before pushing:");
|
||||
for (const stale of staleScripts) {
|
||||
log.warn(stale);
|
||||
}
|
||||
|
||||
log.info("")
|
||||
}
|
||||
|
||||
for (const change of tracker.flows) {
|
||||
const stale = await generateFlowLockInternal(change, true, workspace, false, true);
|
||||
if (stale) {
|
||||
staleFlows.push(stale);
|
||||
}
|
||||
}
|
||||
|
||||
if (staleFlows.length > 0) {
|
||||
log.warn("Stale flows locks found, you may want to update them using 'wmill flow generate-locks' before pushing:");
|
||||
for (const stale of staleFlows) {
|
||||
log.warn(stale);
|
||||
}
|
||||
log.info("")
|
||||
}
|
||||
|
||||
const version = await fetchVersion(workspace.remote);
|
||||
|
||||
log.info(colors.gray("Remote version: " + version));
|
||||
@@ -1264,7 +1393,9 @@ export async function push(opts: GlobalOptions & SyncOptions) {
|
||||
log.info(colors.gray(`Applying changes to files ...`));
|
||||
|
||||
const alreadySynced: string[] = [];
|
||||
const globalDeps = await findGlobalDeps();
|
||||
|
||||
|
||||
|
||||
|
||||
for await (const change of changes) {
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
|
||||
@@ -1512,24 +1643,13 @@ const command = new Command()
|
||||
)
|
||||
.command("pull")
|
||||
.description("Pull any remote changes and apply them locally.")
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
)
|
||||
.option(
|
||||
"--raw",
|
||||
"Push without using state, just overwrite. (Default, has no effect)"
|
||||
)
|
||||
.option("--yes", "Pull without needing confirmation")
|
||||
.option(
|
||||
"--stateful",
|
||||
"Pull using state tracking (create .wmill folder and needed for --fail-conflicts)"
|
||||
)
|
||||
.option("--plain-secrets", "Pull secrets as plain text")
|
||||
.option("--json", "Use JSON instead of YAML")
|
||||
.option("--skip-variables", "Skip syncing variables (including secrets)")
|
||||
.option("--skip-secrets", "Skip syncing only secrets variables")
|
||||
.option("--skip-resources", "Skip syncing resources")
|
||||
// .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic")
|
||||
.option("--include-schedules", "Include syncing schedules")
|
||||
.option("--include-users", "Include syncing users")
|
||||
.option("--include-groups", "Include syncing groups")
|
||||
@@ -1551,25 +1671,13 @@ const command = new Command()
|
||||
.action(pull as any)
|
||||
.command("push")
|
||||
.description("Push any local changes and apply them remotely.")
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
)
|
||||
.option(
|
||||
"--raw",
|
||||
"Push without using state, just overwrite. (Default, has no effect)"
|
||||
)
|
||||
.option(
|
||||
"--stateful",
|
||||
"Pull using state tracking (use .wmill folder and needed for --fail-conflicts)w"
|
||||
)
|
||||
.option("--skip-pull", "(stateful only) Push without pulling first")
|
||||
.option("--yes", "Push without needing confirmation")
|
||||
.option("--plain-secrets", "Push secrets as plain text")
|
||||
.option("--json", "Use JSON instead of YAML")
|
||||
.option("--skip-variables", "Skip syncing variables (including secrets)")
|
||||
.option("--skip-secrets", "Skip syncing only secrets variables")
|
||||
.option("--skip-resources", "Skip syncing resources")
|
||||
// .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic")
|
||||
.option("--include-schedules", "Include syncing schedules")
|
||||
.option("--include-users", "Include syncing users")
|
||||
.option("--include-groups", "Include syncing groups")
|
||||
|
||||
+4
-3
@@ -79,7 +79,8 @@ export function showDiff(local: string, remote: string) {
|
||||
log.info("Diff too large to display");
|
||||
return;
|
||||
}
|
||||
for (const part of Diff.diffLines(local, remote)) {
|
||||
|
||||
for (const part of Diff.diffLines(local ?? '', remote ?? '')) {
|
||||
if (part.removed) {
|
||||
// print red if removed without newline
|
||||
finalString += `\x1b[31m${part.value}\x1b[0m`;
|
||||
@@ -157,8 +158,8 @@ export function parseFromPath(p: string, content: string): any {
|
||||
return p.endsWith(".yaml")
|
||||
? yamlParseContent(p, content)
|
||||
: p.endsWith(".json")
|
||||
? JSON.parse(content)
|
||||
: content;
|
||||
? JSON.parse(content)
|
||||
: content;
|
||||
}
|
||||
export function parseFromFile(p: string): any {
|
||||
if (p.endsWith(".json")) {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
windmill-api/
|
||||
node_modules
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"deno.unstable": true
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# windmill-deno-client
|
||||
|
||||
Deno client for Windmill
|
||||
|
||||
## Howto build locally
|
||||
|
||||
run `./build.sh`
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
npm ci --ignore-scripts
|
||||
npx --yes openapi-typescript-codegen --input ../backend/windmill-api/openapi.yaml \
|
||||
--output ./src --useOptions \
|
||||
&& sed -i '213 i \\ request.referrerPolicy = \"no-referrer\"\n' src/core/request.ts
|
||||
npx --yes denoify
|
||||
rm -rf windmill-api
|
||||
mv deno_dist windmill-api
|
||||
sed -i 's/buffer DENOIFY: UNKNOWN NODE BUILTIN/node:buffer/' ./windmill-api/core/request.ts
|
||||
rm -rf src/
|
||||
@@ -1,319 +0,0 @@
|
||||
import {
|
||||
JobService,
|
||||
ResourceService,
|
||||
VariableService,
|
||||
} from "./windmill-api/index.ts";
|
||||
import { OpenAPI } from "./windmill-api/index.ts";
|
||||
|
||||
export {
|
||||
AdminService,
|
||||
AuditService,
|
||||
FlowService,
|
||||
GranularAclService,
|
||||
GroupService,
|
||||
JobService,
|
||||
ResourceService,
|
||||
VariableService,
|
||||
ScriptService,
|
||||
ScheduleService,
|
||||
SettingsService,
|
||||
UserService,
|
||||
WorkspaceService,
|
||||
} from "./windmill-api/index.ts";
|
||||
|
||||
export type Sql = string;
|
||||
export type Email = string;
|
||||
export type Base64 = string;
|
||||
export type Resource<S extends string> = any;
|
||||
|
||||
export const SHARED_FOLDER = "/shared";
|
||||
|
||||
export function setClient(token: string, baseUrl: string) {
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
OpenAPI.TOKEN = token;
|
||||
OpenAPI.BASE = baseUrl + "/api";
|
||||
}
|
||||
|
||||
setClient(
|
||||
Deno.env.get("WM_TOKEN") ?? "no_token",
|
||||
Deno.env.get("BASE_INTERNAL_URL") ??
|
||||
Deno.env.get("BASE_URL") ??
|
||||
"http://localhost:8000"
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a client configuration from env variables
|
||||
* @returns client configuration
|
||||
*/
|
||||
export function getWorkspace(): string {
|
||||
return Deno.env.get("WM_WORKSPACE") ?? "no_workspace";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource value by path
|
||||
* @param path path of the resource, default to internal state path
|
||||
* @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error
|
||||
* @returns resource value
|
||||
*/
|
||||
export async function getResource(
|
||||
path?: string,
|
||||
undefinedIfEmpty?: boolean
|
||||
): Promise<any> {
|
||||
const workspace = getWorkspace();
|
||||
path = path ?? getStatePath();
|
||||
try {
|
||||
return await ResourceService.getResourceValueInterpolated({
|
||||
workspace,
|
||||
path,
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (undefinedIfEmpty && e.status === 404) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw Error(`Resource not found at ${path} or not visible to you`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a resource value in case the default value was picked because the input payload was undefined
|
||||
* @param obj resource value or path of the resource under the format `$res:path`
|
||||
* @returns resource value
|
||||
*/
|
||||
export async function resolveDefaultResource(obj: any): Promise<any> {
|
||||
if (typeof obj === "string" && obj.startsWith("$res:")) {
|
||||
return await getResource(obj.substring(5), true);
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStatePath(): string {
|
||||
const state_path = Deno.env.get("WM_STATE_PATH_NEW") ?? Deno.env.get("WM_STATE_PATH");
|
||||
if (state_path === undefined) {
|
||||
throw Error("State path not set");
|
||||
}
|
||||
return state_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a resource value by path
|
||||
* @param value new value of the resource to set
|
||||
* @param path path of the resource to set, default to state path
|
||||
* @param initializeToTypeIfNotExist if the resource does not exist, initialize it with this type
|
||||
*/
|
||||
export async function setResource(
|
||||
value: any,
|
||||
path?: string,
|
||||
initializeToTypeIfNotExist?: string
|
||||
): Promise<void> {
|
||||
path = path ?? getStatePath();
|
||||
const workspace = getWorkspace();
|
||||
if (await ResourceService.existsResource({ workspace, path })) {
|
||||
await ResourceService.updateResourceValue({
|
||||
workspace,
|
||||
path,
|
||||
requestBody: { value },
|
||||
});
|
||||
} else if (initializeToTypeIfNotExist) {
|
||||
await ResourceService.createResource({
|
||||
workspace,
|
||||
requestBody: { path, value, resource_type: initializeToTypeIfNotExist },
|
||||
});
|
||||
} else {
|
||||
throw Error(
|
||||
`Resource at path ${path} does not exist and no type was provided to initialize it`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state
|
||||
* @param state state to set
|
||||
* @deprecated use setState instead
|
||||
*/
|
||||
export async function setInternalState(state: any): Promise<void> {
|
||||
await setResource(state, undefined, "state");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state
|
||||
* @param state state to set
|
||||
*/
|
||||
export async function setState(state: any): Promise<void> {
|
||||
await setResource(state, undefined, "state");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shared state
|
||||
* @param state state to set
|
||||
*/
|
||||
export async function setSharedState(
|
||||
state: any,
|
||||
path = "state.json"
|
||||
): Promise<void> {
|
||||
await Deno.writeTextFile(SHARED_FOLDER + "/" + path, JSON.stringify(state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared state
|
||||
* @param state state to set
|
||||
*/
|
||||
export async function getSharedState(path = "state.json"): Promise<any> {
|
||||
return JSON.parse(await Deno.readTextFile(SHARED_FOLDER + "/" + path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal state
|
||||
* @deprecated use getState instead
|
||||
*/
|
||||
export async function getInternalState(): Promise<any> {
|
||||
return await getResource(getStatePath(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state shared across executions
|
||||
*/
|
||||
export async function getState(): Promise<any> {
|
||||
return await getResource(getStatePath(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a variable by path
|
||||
* @param path path of the variable
|
||||
* @returns variable value
|
||||
*/
|
||||
export async function getVariable(path: string): Promise<string | undefined> {
|
||||
const workspace = getWorkspace();
|
||||
try {
|
||||
return await VariableService.getVariableValue({ workspace, path });
|
||||
} catch (e: any) {
|
||||
throw Error(`Variable not found at ${path} or not visible to you`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a variable by path, create if not exist
|
||||
* @param path path of the variable
|
||||
* @param value value of the variable
|
||||
* @param isSecretIfNotExist if the variable does not exist, create it as secret or not (default: false)
|
||||
* @param descriptionIfNotExist if the variable does not exist, create it with this description (default: "")
|
||||
*/
|
||||
export async function setVariable(
|
||||
path: string,
|
||||
value: string,
|
||||
isSecretIfNotExist?: boolean,
|
||||
descriptionIfNotExist?: string
|
||||
): Promise<void> {
|
||||
const workspace = getWorkspace();
|
||||
if (await VariableService.existsVariable({ workspace, path })) {
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path,
|
||||
requestBody: { value },
|
||||
});
|
||||
} else {
|
||||
await VariableService.createVariable({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path,
|
||||
value,
|
||||
is_secret: isSecretIfNotExist ?? false,
|
||||
description: descriptionIfNotExist ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function databaseUrlFromResource(path: string): Promise<string> {
|
||||
const resource = await getResource(path);
|
||||
return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URLs needed for resuming a flow after this step
|
||||
* @param approver approver name
|
||||
* @returns approval page UI URL, resume and cancel API URLs for resumeing the flow
|
||||
*/
|
||||
export async function getResumeUrls(approver?: string): Promise<{
|
||||
approvalPage: string;
|
||||
resume: string;
|
||||
cancel: string;
|
||||
}> {
|
||||
const nonce = Math.floor(Math.random() * 4294967295);
|
||||
const workspace = getWorkspace();
|
||||
return await JobService.getResumeUrls({
|
||||
workspace,
|
||||
resumeId: nonce,
|
||||
approver,
|
||||
id: Deno.env.get("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getResumeUrls instead
|
||||
*/
|
||||
export function getResumeEndpoints(approver?: string): Promise<{
|
||||
approvalPage: string;
|
||||
resume: string;
|
||||
cancel: string;
|
||||
}> {
|
||||
return getResumeUrls(approver);
|
||||
}
|
||||
|
||||
export function base64ToUint8Array(data: string): Uint8Array {
|
||||
return Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
|
||||
let base64 = "";
|
||||
const encodings =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
const bytes = new Uint8Array(arrayBuffer);
|
||||
const byteLength = bytes.byteLength;
|
||||
const byteRemainder = byteLength % 3;
|
||||
const mainLength = byteLength - byteRemainder;
|
||||
|
||||
let a, b, c, d;
|
||||
let chunk;
|
||||
|
||||
// Main loop deals with bytes in chunks of 3
|
||||
for (let i = 0; i < mainLength; i = i + 3) {
|
||||
// Combine the three bytes into a single integer
|
||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
||||
|
||||
// Use bitmasks to extract 6-bit segments from the triplet
|
||||
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
|
||||
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
|
||||
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
|
||||
d = chunk & 63; // 63 = 2^6 - 1
|
||||
|
||||
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
|
||||
}
|
||||
|
||||
// Deal with the remaining bytes and padding
|
||||
if (byteRemainder == 1) {
|
||||
chunk = bytes[mainLength];
|
||||
|
||||
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
|
||||
|
||||
// Set the 4 least significant bits to zero
|
||||
b = (chunk & 3) << 4; // 3 = 2^2 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + "==";
|
||||
} else if (byteRemainder == 2) {
|
||||
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
|
||||
|
||||
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
|
||||
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
|
||||
|
||||
// Set the 2 least significant bits to zero
|
||||
c = (chunk & 15) << 2; // 15 = 2^4 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + "=";
|
||||
}
|
||||
|
||||
return base64;
|
||||
}
|
||||
Generated
-1512
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"denoify": {
|
||||
"index": "index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"denoify": "^1.3.1",
|
||||
"openapi-typescript-codegen": "^0.23.0"
|
||||
},
|
||||
"overrides": {
|
||||
"evt": "2.4.6"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "ES6",
|
||||
"lib": ["es7", "es6", "dom"],
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -159,6 +159,7 @@ services:
|
||||
# Configure the mounted Caddyfile and the exposed ports or use another reverse proxy if needed
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
# - ./certs:/certs # Provide custom certificate files like cert.pem and key.pem to enable HTTPS - See the corresponding section in the Caddyfile
|
||||
ports:
|
||||
# To change the exposed port, simply change 80:80 to <desired_port>:80. No other changes needed
|
||||
@@ -176,3 +177,4 @@ volumes:
|
||||
worker_logs: null
|
||||
windmill_index: null
|
||||
lsp_cache: null
|
||||
caddy_data: null
|
||||
|
||||
+21
-1
@@ -1,3 +1,22 @@
|
||||
FROM alpine:3.14 AS oracledb-client
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Oracle DB Client for amd64
|
||||
COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/amd64/lib
|
||||
|
||||
# Oracle DB Client for arm64
|
||||
RUN mkdir -p /opt/oracle/23/arm64 \
|
||||
&& cd /opt/oracle/23/arm64 \
|
||||
&& wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linux-arm64.zip \
|
||||
&& unzip instantclient-basiclite-linux-arm64.zip && rm instantclient-basiclite-linux-arm64.zip && mv instantclient* ./lib
|
||||
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||
mv /opt/oracle/23/arm64/lib /opt/oracle/23/lib; \
|
||||
else \
|
||||
mv /opt/oracle/23/amd64/lib /opt/oracle/23/lib; \
|
||||
fi
|
||||
|
||||
FROM ghcr.io/windmill-labs/windmill-ee:dev
|
||||
|
||||
COPY --from=rust:1.81.0 /usr/local/cargo /usr/local/cargo
|
||||
@@ -5,11 +24,12 @@ COPY --from=rust:1.81.0 /usr/local/rustup /usr/local/rustup
|
||||
|
||||
RUN pip3 install ansible
|
||||
|
||||
# dotnet SDK
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
ENV DOTNET_ROOT="/opt/dotnet-sdk/bin"
|
||||
|
||||
# Oracle DB Client
|
||||
COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/lib
|
||||
COPY --from=oracledb-client /opt/oracle/23/lib /opt/oracle/23/lib
|
||||
RUN apt-get -y update && apt-get install -y libaio1
|
||||
RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig
|
||||
|
||||
+21
-1
@@ -21,6 +21,25 @@ RUN apt-get -y update \
|
||||
RUN git clone -b master --single-branch https://github.com/google/nsjail.git . && git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800
|
||||
RUN make
|
||||
|
||||
FROM alpine:3.14 AS oracledb-client
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Oracle DB Client for amd64
|
||||
COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/amd64/lib
|
||||
|
||||
# Oracle DB Client for arm64
|
||||
RUN mkdir -p /opt/oracle/23/arm64 \
|
||||
&& cd /opt/oracle/23/arm64 \
|
||||
&& wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linux-arm64.zip \
|
||||
&& unzip instantclient-basiclite-linux-arm64.zip && rm instantclient-basiclite-linux-arm64.zip && mv instantclient* ./lib
|
||||
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||
mv /opt/oracle/23/arm64/lib /opt/oracle/23/lib; \
|
||||
else \
|
||||
mv /opt/oracle/23/amd64/lib /opt/oracle/23/lib; \
|
||||
fi
|
||||
|
||||
FROM ghcr.io/windmill-labs/windmill-ee:dev
|
||||
|
||||
RUN apt-get update && apt-get install -y libprotobuf-dev libnl-route-3-dev
|
||||
@@ -30,12 +49,13 @@ COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
|
||||
RUN pip3 install ansible
|
||||
|
||||
# dotnet SDK
|
||||
COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk
|
||||
RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet
|
||||
ENV DOTNET_ROOT="/opt/dotnet-sdk/bin"
|
||||
|
||||
# Oracle DB Client
|
||||
COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/lib
|
||||
COPY --from=oracledb-client /opt/oracle/23/lib /opt/oracle/23/lib
|
||||
RUN apt-get -y update && apt-get install -y libaio1
|
||||
RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
overlays = [ (import rust-overlay) ];
|
||||
};
|
||||
rust = pkgs.rust-bin.stable.latest.default.override {
|
||||
@@ -49,7 +50,7 @@
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
git xcaddy sqlx-cli flock sccache
|
||||
deno python3 python3Packages.pip go bun uv
|
||||
deno python3 python3Packages.pip go bun uv dotnet-sdk_9 oracle-instantclient
|
||||
]);
|
||||
packages = [
|
||||
(pkgs.writeScriptBin "wm-caddy" ''
|
||||
@@ -102,12 +103,15 @@
|
||||
UV_PATH = "${pkgs.uv}/bin/uv";
|
||||
FLOCK_PATH = "${pkgs.flock}/bin/flock";
|
||||
CARGO_PATH = "${rust}/bin/cargo";
|
||||
DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet";
|
||||
DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet";
|
||||
ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib";
|
||||
};
|
||||
packages.default = self.packages.${system}.windmill;
|
||||
packages.windmill-client = pkgs.buildNpmPackage {
|
||||
name = "windmill-client";
|
||||
version = (pkgs.lib.strings.trim (builtins.readFile ./version.txt));
|
||||
|
||||
|
||||
src = pkgs.nix-gitignore.gitignoreSource [] ./frontend;
|
||||
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||
buildInputs = with pkgs; [ nodejs pixman cairo pango ];
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.448.1",
|
||||
"version": "1.454.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.448.1",
|
||||
"version": "1.454.1",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.32.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.448.1",
|
||||
"version": "1.454.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
import { base } from '$lib/base'
|
||||
import { emptySchema, emptyString } from '$lib/utils'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import type SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import TestConnection from './TestConnection.svelte'
|
||||
import SupabaseIcon from './icons/SupabaseIcon.svelte'
|
||||
import Popup from './common/popup/Popup.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
export let resourceType: string
|
||||
export let resourceTypeInfo: ResourceType | undefined
|
||||
@@ -195,13 +196,17 @@
|
||||
>{error}</span
|
||||
>{:else}<div class="py-2" />{/if}
|
||||
<div class="h-full w-full border p-1 rounded">
|
||||
<SimpleEditor
|
||||
bind:this={rawCodeEditor}
|
||||
autoHeight
|
||||
lang="json"
|
||||
bind:code={rawCode}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{#await import('$lib/components/SimpleEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:this={rawCodeEditor}
|
||||
autoHeight
|
||||
lang="json"
|
||||
bind:code={rawCode}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{/await}
|
||||
</div>
|
||||
{:else if resourceTypeInfo?.format_extension}
|
||||
<h5 class="mt-4 inline-flex items-center gap-4">
|
||||
@@ -209,13 +214,17 @@
|
||||
</h5>
|
||||
<div class="py-2" />
|
||||
<div class="h-full w-full border p-1 rounded">
|
||||
<SimpleEditor
|
||||
bind:this={rawCodeEditor}
|
||||
autoHeight
|
||||
lang={resourceTypeInfo.format_extension}
|
||||
bind:code={textFileContent}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{#await import('$lib/components/SimpleEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:this={rawCodeEditor}
|
||||
autoHeight
|
||||
lang={resourceTypeInfo.format_extension}
|
||||
bind:code={textFileContent}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{/await}
|
||||
</div>
|
||||
{:else}
|
||||
<SchemaForm
|
||||
|
||||
@@ -174,13 +174,21 @@
|
||||
}
|
||||
|
||||
function popupListener(event) {
|
||||
console.log('Received oauth popup message')
|
||||
let data = event.data
|
||||
if (event.origin == null || event.origin !== window.location.origin) {
|
||||
console.log(
|
||||
'Received oauth popup message from different origin',
|
||||
event.origin,
|
||||
window.location.origin
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
window.removeEventListener('message', popupListener)
|
||||
processPopupData(data)
|
||||
if (data.type == 'success' || data.type == 'error') {
|
||||
window.removeEventListener('message', popupListener)
|
||||
processPopupData(data)
|
||||
}
|
||||
}
|
||||
|
||||
function handleStorageEvent(event) {
|
||||
@@ -205,6 +213,7 @@
|
||||
})
|
||||
|
||||
function processPopupData(data) {
|
||||
console.log('Processing oauth popup data')
|
||||
if (data.type === 'error') {
|
||||
sendUserToast(data.error, true)
|
||||
step = 2
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
emptyString,
|
||||
getSchemaFromProperties
|
||||
} from '$lib/utils'
|
||||
import { DollarSign, Pipette, Plus, X, Check } from 'lucide-svelte'
|
||||
import { DollarSign, Pipette, Plus, X, Check, Loader2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte'
|
||||
import Multiselect from 'svelte-multiselect'
|
||||
import { fade } from 'svelte/transition'
|
||||
import JsonEditor from './apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import { Button, SecondsInput } from './common'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import Range from './Range.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import type SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import type VariableEditor from './VariableEditor.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -38,6 +37,7 @@
|
||||
import DynSelect from './DynSelect.svelte'
|
||||
import type { Script } from '$lib/gen'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils'
|
||||
import type { ComponentCustomCSS } from './apps/types'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
@@ -101,6 +101,20 @@
|
||||
export let hideNested = false
|
||||
export let nestedParent: { label: string; nestedParent: any | undefined } | undefined = undefined
|
||||
export let nestedClasses = ''
|
||||
export let displayType = true
|
||||
export let css: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined
|
||||
export let appPath: string | undefined = undefined
|
||||
export let computeS3ForceViewerPolicies:
|
||||
| (() =>
|
||||
| {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
allow_workspace_resource: boolean
|
||||
file_key_regex: string
|
||||
}
|
||||
| undefined)
|
||||
| undefined = undefined
|
||||
export let workspace: string | undefined = undefined
|
||||
|
||||
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
|
||||
|
||||
@@ -354,8 +368,9 @@
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<div
|
||||
class={twMerge(
|
||||
'flex flex-col w-full rounded-md px-2 relative',
|
||||
'flex flex-col w-full rounded-md relative',
|
||||
minW ? 'min-w-[250px]' : '',
|
||||
diffStatus?.diff ? 'px-2' : '',
|
||||
diffStatus?.diff == 'added'
|
||||
? 'bg-green-300 dark:bg-green-800'
|
||||
: diffStatus?.diff === 'removed'
|
||||
@@ -366,11 +381,11 @@
|
||||
? 'border-2 border-green-500 bg-surface'
|
||||
: ''
|
||||
)}
|
||||
data-schema-picker
|
||||
>
|
||||
{#if diffStatus && typeof diffStatus === 'object' && diffStatus.diff !== 'same'}
|
||||
<div
|
||||
class="absolute top-0 right-2 rounded-md rounded-t-none flex flex-row overflow-hidden bg-surface"
|
||||
data-schema-picker
|
||||
>
|
||||
<button
|
||||
class="p-1 bg-green-500 text-white hover:bg-green-600"
|
||||
@@ -401,11 +416,13 @@
|
||||
{format}
|
||||
{simpleTooltip}
|
||||
{lightHeader}
|
||||
{displayType}
|
||||
labelClass={css?.label?.class}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if description}
|
||||
<div class="text-xs italic pb-1 text-secondary">
|
||||
<div class={twMerge('text-xs italic pb-1 text-secondary', css?.description?.class)}>
|
||||
<pre class="font-main whitespace-normal">{description}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -511,7 +528,11 @@
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined}
|
||||
<JsonEditor code={JSON.stringify(v, null, 2)} bind:value={v} />
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default code={JSON.stringify(v, null, 2)} bind:value={v} />
|
||||
{/await}
|
||||
{:else if Array.isArray(itemsType?.enum)}
|
||||
<ArgEnum
|
||||
required
|
||||
@@ -538,17 +559,21 @@
|
||||
defaultValue={undefined}
|
||||
/>
|
||||
{:else if itemsType?.type == 'resource'}
|
||||
<JsonEditor
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={JSON.stringify(v, null, 2)}
|
||||
bind:value={v}
|
||||
/>
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={JSON.stringify(v, null, 2)}
|
||||
bind:value={v}
|
||||
/>
|
||||
{/await}
|
||||
{:else if itemsType?.type === 'object' && itemsType?.properties}
|
||||
<div class="p-8 border rounded-md w-full">
|
||||
<SchemaForm
|
||||
@@ -669,17 +694,21 @@
|
||||
options={{ left: 'Raw S3 object input' }}
|
||||
/>
|
||||
{#if s3FileUploadRawMode}
|
||||
<JsonEditor
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={JSON.stringify(value ?? defaultValue ?? { s3: '' }, null, 2)}
|
||||
bind:value
|
||||
/>
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={JSON.stringify(value ?? defaultValue ?? { s3: '' }, null, 2)}
|
||||
bind:value
|
||||
/>
|
||||
{/await}
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
@@ -694,6 +723,9 @@
|
||||
</Button>
|
||||
{:else}
|
||||
<FileUpload
|
||||
{appPath}
|
||||
computeForceViewerPolicies={computeS3ForceViewerPolicies}
|
||||
{workspace}
|
||||
allowMultiple={false}
|
||||
randomFileKey={true}
|
||||
on:addition={(evt) => {
|
||||
@@ -787,33 +819,41 @@
|
||||
{:else if disabled}
|
||||
<textarea disabled />
|
||||
{:else}
|
||||
<JsonEditor
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if disabled}
|
||||
<textarea disabled />
|
||||
{:else}
|
||||
<JsonEditor
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if properties && Object.keys(properties).length > 0 && inputCat !== 'list'}
|
||||
@@ -884,17 +924,21 @@
|
||||
{:else if disabled}
|
||||
<textarea disabled />
|
||||
{:else}
|
||||
<JsonEditor
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
bind:editor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
code={rawValue}
|
||||
bind:value
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
{#if inputCat == 'list'}
|
||||
<div class="block">
|
||||
@@ -937,18 +981,22 @@
|
||||
{/if}
|
||||
{:else if inputCat == 'sql' || inputCat == 'yaml'}
|
||||
<div class="border my-1 mb-4 w-full border-primary">
|
||||
<SimpleEditor
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
bind:this={editor}
|
||||
lang={inputCat}
|
||||
bind:code={value}
|
||||
autoHeight
|
||||
/>
|
||||
{#await import('$lib/components/SimpleEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
on:focus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
bind:this={editor}
|
||||
lang={inputCat}
|
||||
bind:code={value}
|
||||
autoHeight
|
||||
/>
|
||||
{/await}
|
||||
</div>
|
||||
{:else if inputCat == 'base64'}
|
||||
<div class="flex flex-col my-6 w-full">
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import CaptureTable from '$lib/components/triggers/CaptureTable.svelte'
|
||||
|
||||
export let flowPath: string | null = null
|
||||
export let headless: boolean = true
|
||||
export let addButton: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="h-full">
|
||||
<CaptureTable
|
||||
path={flowPath ?? ''}
|
||||
on:select
|
||||
isFlow={true}
|
||||
{headless}
|
||||
{addButton}
|
||||
on:openTriggers
|
||||
/>
|
||||
</div>
|
||||
@@ -7,7 +7,6 @@
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import GraphqlSchemaViewer from './GraphqlSchemaViewer.svelte'
|
||||
import { Loader2, RefreshCcw } from 'lucide-svelte'
|
||||
import {
|
||||
formatGraphqlSchema,
|
||||
@@ -93,7 +92,11 @@
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
{#if dbSchema.lang === 'graphql'}
|
||||
<GraphqlSchemaViewer code={formatGraphqlSchema(dbSchema.schema)} class="h-full" />
|
||||
{#await import('$lib/components/GraphqlSchemaViewer.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default code={formatGraphqlSchema(dbSchema.schema)} class="h-full" />
|
||||
{/await}
|
||||
{:else}
|
||||
<ObjectViewer json={formatSchema(dbSchema)} pureViewer collapseLevel={1} />
|
||||
{/if}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import DarkModeObserver from './DarkModeObserver.svelte'
|
||||
import ResultJobLoader from './ResultJobLoader.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import JsonEditor from './apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
export let value: any = undefined
|
||||
export let helperScript:
|
||||
@@ -149,6 +149,10 @@
|
||||
<div class="text-xs text-tertiary"
|
||||
>Dynamic Select is not available in this mode, write value directly</div
|
||||
>
|
||||
<JsonEditor code={rawCode} bind:value />
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default code={rawCode} bind:value />
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
export let noPreview: boolean = false
|
||||
export let jsonEnabled: boolean = true
|
||||
export let isAppInput: boolean = false
|
||||
export let lightweightMode: boolean = false
|
||||
export let displayWebhookWarning: boolean = false
|
||||
export let dndType: string | undefined = undefined
|
||||
export let editTab:
|
||||
@@ -52,6 +51,7 @@
|
||||
export let diff: Record<string, SchemaDiff> = {}
|
||||
export let disableDnd: boolean = false
|
||||
export let shouldDispatchChanges: boolean = false
|
||||
export let isValid: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -295,7 +295,6 @@
|
||||
schema = schema
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
{lightweightMode}
|
||||
prettifyHeader={isAppInput}
|
||||
disabled={!!previewSchema}
|
||||
{diff}
|
||||
@@ -305,6 +304,7 @@
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
{shouldDispatchChanges}
|
||||
bind:isValid
|
||||
/>
|
||||
|
||||
<slot name="runButton" />
|
||||
@@ -557,7 +557,6 @@
|
||||
bind:defaultValue={schema.properties[argName].default}
|
||||
{variableEditor}
|
||||
{itemPicker}
|
||||
{lightweightMode}
|
||||
bind:nullable={schema.properties[argName].nullable}
|
||||
bind:disabled={schema.properties[argName].disabled}
|
||||
type={schema.properties[argName].type}
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { type Job, JobService, type Flow, type RestartedFrom, type OpenFlow } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { Badge, Button, Drawer, Popup } from './common'
|
||||
import { Badge, Button, Popup } from './common'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import { runFlowPreview } from './flows/utils'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SchemaFormWithArgPicker from './SchemaFormWithArgPicker.svelte'
|
||||
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
|
||||
import FlowProgressBar from './flows/FlowProgressBar.svelte'
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
CornerDownLeft,
|
||||
Play,
|
||||
RefreshCw,
|
||||
X,
|
||||
ArrowLeftIcon
|
||||
} from 'lucide-svelte'
|
||||
import { AlertTriangle, ArrowRight, CornerDownLeft, Play, RefreshCw, X } from 'lucide-svelte'
|
||||
import { emptyString, sendUserToast } from '$lib/utils'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import SavedInputs from './SavedInputs.svelte'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { sliceModules } from './flows/flowStateUtils'
|
||||
import CapturesInputs from '$lib/components/CapturesInputs.svelte'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import JsonInputs from './JsonInputs.svelte'
|
||||
|
||||
export let previewMode: 'upTo' | 'whole'
|
||||
export let open: boolean
|
||||
export let preventEscape = false
|
||||
|
||||
export let jobId: string | undefined = undefined
|
||||
export let job: Job | undefined = undefined
|
||||
@@ -39,6 +32,10 @@
|
||||
|
||||
let isRunning: boolean = false
|
||||
let jobProgressReset: () => void
|
||||
let jsonView: boolean = false
|
||||
let jsonEditor: JsonInputs
|
||||
let schemaHeight = 0
|
||||
let isValid: boolean = true
|
||||
|
||||
export function test() {
|
||||
renderCount++
|
||||
@@ -83,6 +80,10 @@
|
||||
const newFlow = extractFlow(previewMode)
|
||||
jobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
isRunning = true
|
||||
if (inputSelected) {
|
||||
savedArgs = $previewArgs
|
||||
inputSelected = undefined
|
||||
}
|
||||
} catch (e) {
|
||||
sendUserToast('Could not run preview', true, undefined, e.toString())
|
||||
isRunning = false
|
||||
@@ -99,6 +100,14 @@
|
||||
runPreview($previewArgs, undefined)
|
||||
}
|
||||
break
|
||||
|
||||
case 'Escape':
|
||||
if (preventEscape) {
|
||||
selectInput(undefined)
|
||||
event.preventDefault()
|
||||
event.stopPropagation
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,97 +133,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
let savedArgs = $previewArgs
|
||||
let inputSelected: 'captures' | 'history' | 'saved' | undefined = undefined
|
||||
async function selectInput(input, type?: 'captures' | 'history' | 'saved' | undefined) {
|
||||
if (!input) {
|
||||
$previewArgs = savedArgs
|
||||
inputSelected = undefined
|
||||
setTimeout(() => {
|
||||
preventEscape = false
|
||||
}, 100)
|
||||
} else {
|
||||
$previewArgs = input
|
||||
inputSelected = type
|
||||
preventEscape = true
|
||||
jsonEditor?.setCode(JSON.stringify($previewArgs ?? {}, null, '\t'))
|
||||
}
|
||||
}
|
||||
|
||||
export function refresh() {
|
||||
renderCount++
|
||||
}
|
||||
|
||||
$: if (job?.type === 'CompletedJob') {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
$: selectedJobStep !== undefined && onSelectedJobStepChange()
|
||||
|
||||
let inputLibraryDrawer: Drawer
|
||||
let captureLibraryDrawer: Drawer
|
||||
let renderCount: number = 0
|
||||
|
||||
let selectedCapture: Record<string, any> | undefined = undefined
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
<Drawer bind:this={inputLibraryDrawer}>
|
||||
<DrawerContent title="Input library {initialPath}" on:close={inputLibraryDrawer?.toggleDrawer}>
|
||||
<SavedInputs
|
||||
flowPath={initialPath}
|
||||
isValid={true}
|
||||
args={$previewArgs}
|
||||
on:selected_args={(e) => {
|
||||
$previewArgs = JSON.parse(JSON.stringify(e.detail))
|
||||
inputLibraryDrawer?.closeDrawer()
|
||||
renderCount++
|
||||
}}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Drawer bind:this={captureLibraryDrawer}>
|
||||
<DrawerContent
|
||||
title="Trigger captures library {initialPath}"
|
||||
on:close={captureLibraryDrawer?.toggleDrawer}
|
||||
>
|
||||
<div class="h-full flex flex-col gap-2">
|
||||
<div class="min-h-0 grow h-full">
|
||||
<CapturesInputs
|
||||
flowPath={$pathStore}
|
||||
headless={false}
|
||||
addButton={true}
|
||||
on:select={(e) => {
|
||||
selectedCapture = e.detail
|
||||
}}
|
||||
on:openTriggers={(e) => {
|
||||
dispatch('openTriggers', e.detail)
|
||||
captureLibraryDrawer?.closeDrawer()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-2 px-2 py-2 h-[50%]">
|
||||
<div class="w-full flex flex-col">
|
||||
<Button
|
||||
color="blue"
|
||||
btnClasses="w-full"
|
||||
size="sm"
|
||||
spacingSize="xl"
|
||||
on:click={async () => {
|
||||
$previewArgs = JSON.parse(JSON.stringify(selectedCapture))
|
||||
captureLibraryDrawer?.closeDrawer()
|
||||
renderCount++
|
||||
}}
|
||||
disabled={!selectedCapture}
|
||||
>
|
||||
<ArrowLeftIcon class="w-4 h-4 mr-2" />
|
||||
Use input
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-full min-h-0 grow overflow-auto">
|
||||
{#if typeof selectedCapture == 'string' && selectedCapture == 'WINDMILL_TOO_BIG'}
|
||||
<div class="text-secondary mt-2">
|
||||
Payload too big to preview but can still be loaded</div
|
||||
>
|
||||
{:else if Object.keys(selectedCapture || {}).length > 0}
|
||||
<div class=" overflow-auto h-full p-2">
|
||||
<ObjectViewer json={selectedCapture} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-tertiary">
|
||||
Select an Input to preview scripts arguments
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="flex flex-col space-y-2 h-screen bg-surface px-6 py-2 w-full" id="flow-preview-content">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-2">
|
||||
<div class="flex flex-col space-y-2 h-screen bg-surface px-4 py-2 w-full" id="flow-preview-content">
|
||||
<div class="flex flex-row w-full items-center gap-x-2">
|
||||
<div class="w-8">
|
||||
<Button
|
||||
on:click={() => dispatch('close')}
|
||||
@@ -227,28 +179,30 @@
|
||||
</div>
|
||||
|
||||
{#if isRunning}
|
||||
<Button
|
||||
color="red"
|
||||
on:click={async () => {
|
||||
isRunning = false
|
||||
try {
|
||||
jobId &&
|
||||
(await JobService.cancelQueuedJob({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: jobId,
|
||||
requestBody: {}
|
||||
}))
|
||||
} catch {}
|
||||
}}
|
||||
size="sm"
|
||||
btnClasses="w-full max-w-lg"
|
||||
loading={true}
|
||||
clickableWhileLoading
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<div class="mx-auto">
|
||||
<Button
|
||||
color="red"
|
||||
on:click={async () => {
|
||||
isRunning = false
|
||||
try {
|
||||
jobId &&
|
||||
(await JobService.cancelQueuedJob({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: jobId,
|
||||
requestBody: {}
|
||||
}))
|
||||
} catch {}
|
||||
}}
|
||||
size="sm"
|
||||
btnClasses="w-full max-w-lg"
|
||||
loading={true}
|
||||
clickableWhileLoading
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="grow justify-center flex flex-row gap-4">
|
||||
{#if jobId !== undefined && selectedJobStep !== undefined && selectedJobStepIsTopLevel}
|
||||
{#if selectedJobStepType == 'single'}
|
||||
<Button
|
||||
@@ -352,26 +306,6 @@
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
btnClasses="h-full truncate"
|
||||
size="sm"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
captureLibraryDrawer?.openDrawer()
|
||||
}}>Trigger captures library</Button
|
||||
>
|
||||
{#if initialPath != ''}
|
||||
<Button
|
||||
btnClasses="h-full truncate"
|
||||
size="sm"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
inputLibraryDrawer?.openDrawer()
|
||||
}}>Past runs/Input library</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex flex-col gap-y-1">
|
||||
{#if lastPreviewFlow && JSON.stringify($flowStore) != lastPreviewFlow}
|
||||
@@ -387,17 +321,68 @@
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto grow flex flex-col pr-4">
|
||||
<div class="overflow-y-auto grow flex flex-col pt-4">
|
||||
<div class="border-b">
|
||||
{#key renderCount}
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
class="py-4 max-w-3xl"
|
||||
schema={$flowStore.schema}
|
||||
bind:args={$previewArgs}
|
||||
/>
|
||||
{/key}
|
||||
<SchemaFormWithArgPicker
|
||||
runnableId={initialPath == '' ? $pathStore : initialPath}
|
||||
runnableType={'FlowPath'}
|
||||
previewArgs={$previewArgs}
|
||||
on:openTriggers
|
||||
on:select={(e) => {
|
||||
selectInput(e.detail.payload, e.detail?.type)
|
||||
}}
|
||||
{isValid}
|
||||
{jsonView}
|
||||
>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<InputSelectedBadge {inputSelected} />
|
||||
<div class="flex flex-row gap-2">
|
||||
<Toggle
|
||||
bind:checked={jsonView}
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON',
|
||||
rightTooltip: 'Fill args from JSON'
|
||||
}}
|
||||
lightMode
|
||||
on:change={(e) => {
|
||||
jsonEditor?.setCode(JSON.stringify($previewArgs ?? {}, null, '\t'))
|
||||
refresh()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if jsonView}
|
||||
<div class="py-2" style="height: {Math.max(schemaHeight, 100)}px" data-schema-picker>
|
||||
<JsonInputs
|
||||
bind:this={jsonEditor}
|
||||
on:select={(e) => {
|
||||
if (e.detail) {
|
||||
$previewArgs = e.detail
|
||||
}
|
||||
}}
|
||||
updateOnBlur={false}
|
||||
placeholder={`Write args as JSON.<br/><br/>Example:<br/><br/>{<br/> "foo": "12"<br/>}`}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#key renderCount}
|
||||
<div bind:clientHeight={schemaHeight} class="min-h-[40vh]">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
schema={$flowStore.schema}
|
||||
bind:args={$previewArgs}
|
||||
on:change={() => {
|
||||
savedArgs = $previewArgs
|
||||
}}
|
||||
bind:isValid
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
</SchemaFormWithArgPicker>
|
||||
</div>
|
||||
<div class="pt-4 flex flex-col grow">
|
||||
{#if jobId}
|
||||
|
||||
@@ -821,7 +821,7 @@
|
||||
As a non logged in user, you can only see jobs ran by anonymous users like you
|
||||
</Alert>
|
||||
{:else if job}
|
||||
<div class="flow-root w-full space-y-4 {wideResults ? '' : 'max-w-7xl'} mx-auto px-4">
|
||||
<div class="flow-root w-full space-y-4 {wideResults ? '' : 'max-w-7xl px-4'} mx-auto">
|
||||
<!-- {#if innerModules.length > 0 && true}
|
||||
<h3 class="text-md leading-6 font-bold text-primay border-b pb-2">Flow result</h3>
|
||||
{:else}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { X } from 'lucide-svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import LightweightSchemaForm from './LightweightSchemaForm.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { Button } from './common'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
|
||||
export let isOwner: boolean
|
||||
export let workspaceId: string | undefined
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
{#if job?.raw_flow?.modules?.[approvalStep]?.suspend?.resume_form?.schema}
|
||||
<div class="w-full border rounded-lg p-2">
|
||||
<LightweightSchemaForm bind:args={default_payload} {defaultValues} {schema} />
|
||||
<SchemaForm onlyMaskPassword bind:args={default_payload} {defaultValues} {schema} />
|
||||
</div>
|
||||
{/if}
|
||||
<Tooltip
|
||||
|
||||
@@ -10,32 +10,20 @@
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import InfiniteList from './InfiniteList.svelte'
|
||||
|
||||
export let scriptHash: string | null = null
|
||||
export let scriptPath: string | null = null
|
||||
export let flowPath: string | null = null
|
||||
export let runnableId: string | undefined = undefined
|
||||
export let runnableType: RunnableType | undefined = undefined
|
||||
export let loading: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let jobs: Job[] = []
|
||||
let loading: boolean = false
|
||||
let hasMoreCurrentRuns = false
|
||||
let page = 1
|
||||
let infiniteList: InfiniteList | undefined = undefined
|
||||
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
|
||||
|
||||
let runnableType: RunnableType | undefined = undefined
|
||||
$: runnableType = scriptHash
|
||||
? 'ScriptHash'
|
||||
: scriptPath
|
||||
? 'ScriptPath'
|
||||
: flowPath
|
||||
? 'FlowPath'
|
||||
: undefined
|
||||
|
||||
let runnableId: string | undefined = undefined
|
||||
let cachedArgs: Record<string, any> = {}
|
||||
function initLoadInputs() {
|
||||
runnableId = scriptHash || scriptPath || flowPath || undefined
|
||||
|
||||
loadInputsPageFn = async (page: number, perPage: number) => {
|
||||
const inputs = await InputService.getInputHistory({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -48,7 +36,21 @@
|
||||
|
||||
const inputsWithPayload = await Promise.all(
|
||||
inputs.map(async (input) => {
|
||||
if (cachedArgs[input.id]) {
|
||||
return {
|
||||
...input,
|
||||
payloadData: cachedArgs[input.id]
|
||||
}
|
||||
}
|
||||
const payloadData = await loadArgsFromHistory(input.id, undefined, false)
|
||||
if (payloadData === 'WINDMILL_TOO_BIG') {
|
||||
return {
|
||||
...input,
|
||||
payloadData: 'WINDMILL_TOO_BIG',
|
||||
getFullPayload: () => loadArgsFromHistory(input.id, undefined, true)
|
||||
}
|
||||
}
|
||||
cachedArgs[input.id] = payloadData
|
||||
return {
|
||||
...input,
|
||||
payloadData
|
||||
@@ -60,21 +62,24 @@
|
||||
infiniteList?.setLoader(loadInputsPageFn)
|
||||
}
|
||||
|
||||
function handleSelected(data: any) {
|
||||
async function handleSelected(data: any) {
|
||||
if (selected === data.id) {
|
||||
selected = undefined
|
||||
dispatch('select', undefined)
|
||||
resetSelected(true)
|
||||
return
|
||||
}
|
||||
selected = data.id
|
||||
dispatch('select', data.payloadData)
|
||||
if (data.payloadData === 'WINDMILL_TOO_BIG') {
|
||||
const fullPayload = await data.getFullPayload?.()
|
||||
dispatch('select', fullPayload)
|
||||
} else {
|
||||
dispatch('select', structuredClone(data.payloadData))
|
||||
}
|
||||
}
|
||||
|
||||
let selected: string | undefined = undefined
|
||||
|
||||
onDestroy(() => {
|
||||
selected = undefined
|
||||
dispatch('select', undefined)
|
||||
resetSelected(true)
|
||||
})
|
||||
|
||||
async function getPropPickerElements(): Promise<HTMLElement[]> {
|
||||
@@ -100,8 +105,7 @@
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape' && selected) {
|
||||
selected = undefined
|
||||
dispatch('select', undefined)
|
||||
resetSelected(true)
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
@@ -115,14 +119,21 @@
|
||||
|
||||
let jobHovered: string | undefined = undefined
|
||||
|
||||
function refresh() {
|
||||
export function refresh() {
|
||||
if (infiniteList) {
|
||||
infiniteList.loadData('refresh')
|
||||
}
|
||||
}
|
||||
|
||||
export function resetSelected(dispatchEvent?: boolean) {
|
||||
selected = undefined
|
||||
if (dispatchEvent) {
|
||||
dispatch('select', undefined)
|
||||
}
|
||||
}
|
||||
|
||||
$: !loading && refresh()
|
||||
$: $workspaceStore && (scriptHash || scriptPath || flowPath) && infiniteList && initLoadInputs()
|
||||
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
@@ -153,19 +164,18 @@
|
||||
use:clickOutside={{ capture: false, exclude: getPropPickerElements }}
|
||||
on:click_outside={() => {
|
||||
if (selected) {
|
||||
selected = undefined
|
||||
dispatch('select', undefined)
|
||||
resetSelected(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="grow-0">
|
||||
<div class="grow-0" data-schema-picker>
|
||||
<DataTable size="xs" bind:currentPage={page} hasMore={hasMoreCurrentRuns} tableFixed={true}>
|
||||
{#if loading && (jobs == undefined || jobs?.length == 0)}
|
||||
<div class="text-center text-tertiary text-xs py-2">Loading current runs...</div>
|
||||
{:else if jobs?.length > 0}
|
||||
<colgroup>
|
||||
<col class="w-8" />
|
||||
<col class="w-20" />
|
||||
<col class="w-16" />
|
||||
<col />
|
||||
</colgroup>
|
||||
|
||||
@@ -190,7 +200,7 @@
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 grow">
|
||||
<div class="min-h-0 grow" data-schema-picker>
|
||||
<InfiniteList
|
||||
bind:this={infiniteList}
|
||||
selectedItemId={selected}
|
||||
@@ -200,7 +210,7 @@
|
||||
<svelte:fragment slot="columns">
|
||||
<colgroup>
|
||||
<col class="w-8" />
|
||||
<col class="w-20" />
|
||||
<col class="w-16" />
|
||||
<col />
|
||||
</colgroup>
|
||||
</svelte:fragment>
|
||||
@@ -213,7 +223,9 @@
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="empty">
|
||||
<div class="text-center text-tertiary text-xs py-2">No previous Runs</div>
|
||||
<div class="text-center text-tertiary text-xs py-2">
|
||||
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</InfiniteList>
|
||||
</div>
|
||||
|
||||
@@ -56,10 +56,14 @@
|
||||
isNew: false
|
||||
}))
|
||||
}
|
||||
}, 1000)
|
||||
}, 2000)
|
||||
|
||||
page = Math.floor(items.length / perPage) + 1
|
||||
hasMore = items.length === perPage * (page - 1)
|
||||
page = Math.ceil(items.length / perPage)
|
||||
hasMore = items.length === perPage * page
|
||||
if (hasMore) {
|
||||
const potentialNewItems = await loadInputs(page + 1, perPage)
|
||||
hasMore = potentialNewItems.length > 0
|
||||
}
|
||||
initLoad = true
|
||||
isEmpty = items.length === 0
|
||||
length = items.length
|
||||
@@ -112,6 +116,7 @@
|
||||
on:loadMore={() => {
|
||||
loadData()
|
||||
}}
|
||||
{loading}
|
||||
>
|
||||
<slot name="columns" />
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user