Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48152bc5c6 | ||
|
|
4ef47d4bdf | ||
|
|
22cb3b0114 | ||
|
|
95b168d665 | ||
|
|
350a1e7e70 | ||
|
|
88ab5659e7 | ||
|
|
4dfc8f5f91 | ||
|
|
83c0c54613 | ||
|
|
04ddda1b56 | ||
|
|
5c79a35306 | ||
|
|
58b9b9e330 | ||
|
|
5aa251a2d2 | ||
|
|
8ae266b6a9 | ||
|
|
f2dbf6d20d | ||
|
|
5548221092 | ||
|
|
4acd5e526f | ||
|
|
a3cf674cd0 | ||
|
|
f3e62a2e0f | ||
|
|
64a9c4f7d2 | ||
|
|
c3e59fe064 | ||
|
|
478e19379f | ||
|
|
b112c218db | ||
|
|
f371fbeb9b | ||
|
|
2058f27e03 | ||
|
|
423ed04cb9 | ||
|
|
aad43768d4 | ||
|
|
d08c0916f7 | ||
|
|
4e9c22a0bd | ||
|
|
610f90b19e | ||
|
|
09a6e1feaa | ||
|
|
25c9223ba0 | ||
|
|
499d7d4098 | ||
|
|
9b7527c379 | ||
|
|
13216bc2a3 | ||
|
|
88d04b9cbe | ||
|
|
b87d2cc64c | ||
|
|
90b5569c91 | ||
|
|
abcfb2b3b8 | ||
|
|
79ac6312e8 | ||
|
|
da4f57ae59 | ||
|
|
ceeff5f76c | ||
|
|
9add877047 | ||
|
|
b66e038a0f | ||
|
|
2cae72c9db | ||
|
|
767940c6fa | ||
|
|
09082de539 | ||
|
|
65a40b6df1 | ||
|
|
cf5d58ea43 | ||
|
|
e1d659b7d2 | ||
|
|
d062d5e390 | ||
|
|
2c04e04bf0 | ||
|
|
f1029d0f14 | ||
|
|
7215aa9712 | ||
|
|
6426ebf8cb | ||
|
|
fa1bc3c711 | ||
|
|
5c893becfd | ||
|
|
d3fc459b40 | ||
|
|
9925932a28 | ||
|
|
c16bef8f29 | ||
|
|
d50b67f1c8 | ||
|
|
09cdfb4556 | ||
|
|
3dd293e450 | ||
|
|
30eb9aae25 | ||
|
|
cf36fe3bb1 | ||
|
|
d578a0ca80 | ||
|
|
137adbf813 | ||
|
|
0c9d268e2f | ||
|
|
18468e593b | ||
|
|
01e99467e4 | ||
|
|
05c875c613 | ||
|
|
ec4998d866 | ||
|
|
03f9702690 | ||
|
|
0423eeedbe | ||
|
|
c86a080cd6 | ||
|
|
d4340b83ac | ||
|
|
f6c77cf4b3 | ||
|
|
fbab86cc43 | ||
|
|
10fe8883d2 | ||
|
|
1c0f80dc55 | ||
|
|
850e87184e | ||
|
|
839e092aba | ||
|
|
2ee17aea8b | ||
|
|
0639623af6 | ||
|
|
6fac896390 | ||
|
|
77316cbd0e | ||
|
|
5e7a94cab3 | ||
|
|
14e41babfe | ||
|
|
9791eb4568 | ||
|
|
1280617a28 | ||
|
|
c079155e3e | ||
|
|
7a8fd865e2 | ||
|
|
1001643874 | ||
|
|
9e5a69c9e9 | ||
|
|
806a168e18 | ||
|
|
d31b1800be | ||
|
|
d965ccdbc1 | ||
|
|
5b26be6ab4 | ||
|
|
e77574b243 | ||
|
|
ee0e0a214e | ||
|
|
83fec5fd4f | ||
|
|
957a18318a | ||
|
|
ed7878c186 | ||
|
|
c5251a8c33 | ||
|
|
931e3d3c90 | ||
|
|
25c36c1e0a | ||
|
|
ea5b3e6b92 | ||
|
|
4e4c9e31fd | ||
|
|
a76713d556 | ||
|
|
d18c48ac99 | ||
|
|
51cba95d39 | ||
|
|
ce8d5660fe | ||
|
|
26d17141d1 | ||
|
|
2e245accc5 | ||
|
|
84992cd8ff | ||
|
|
fc5034e94d | ||
|
|
e047c3b2b1 | ||
|
|
3dcad57481 | ||
|
|
41a6f89bdb | ||
|
|
2d54dfbf05 | ||
|
|
9e4882c0a9 | ||
|
|
8b2291b0f9 | ||
|
|
f12be4eb19 | ||
|
|
d6421c2ea7 | ||
|
|
b5c21cfe56 | ||
|
|
62ffe9ffce | ||
|
|
408911dbf6 | ||
|
|
3a657b10e7 | ||
|
|
c59183f5c3 | ||
|
|
6045f0c406 | ||
|
|
180b1523f3 | ||
|
|
e1fc963990 | ||
|
|
b07c35fe1c | ||
|
|
9063945161 | ||
|
|
49524d5b28 | ||
|
|
ad43680bb9 | ||
|
|
10e621cffe | ||
|
|
5ef901152f | ||
|
|
ad861f5312 | ||
|
|
5f7e0b7244 | ||
|
|
3bda6da168 | ||
|
|
9e3ea76575 | ||
|
|
db045e7d29 | ||
|
|
192fecc86f | ||
|
|
9d1f9bd39b | ||
|
|
f16231d7c9 | ||
|
|
b0a20a4c00 | ||
|
|
08607509d6 | ||
|
|
75e056bbce | ||
|
|
4c13819434 | ||
|
|
b83eeefd66 | ||
|
|
af6e919765 | ||
|
|
3f0a4e0e64 | ||
|
|
8cb8650460 |
26
.github/workflows/backend-test.yml
vendored
26
.github/workflows/backend-test.yml
vendored
@@ -67,13 +67,23 @@ jobs:
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
- name: Cache DuckDB FFI module build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./backend/windmill-duckdb-ffi-internal/target
|
||||
key: ${{ runner.os }}-duckdb-ffi-${{ hashFiles('./backend/windmill-duckdb-ffi-internal/src/**/*.rs', './backend/windmill-duckdb-ffi-internal/Cargo.toml', './backend/windmill-duckdb-ffi-internal/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-duckdb-ffi-
|
||||
- name: cargo test
|
||||
timeout-minutes: 16
|
||||
run: deno --version && bun -v && go version && python3 --version &&
|
||||
SQLX_OFFLINE=true
|
||||
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
|
||||
DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go)
|
||||
UV_PATH=$(which uv) cargo test --features
|
||||
enterprise,deno_core,license,python,rust,scoped_cache,private --all --
|
||||
--nocapture
|
||||
env:
|
||||
SQLX_OFFLINE: true
|
||||
DATABASE_URL: postgres://postgres:changeme@localhost:5432/windmill
|
||||
DISABLE_EMBEDDING: true
|
||||
RUST_LOG: info
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
run: |
|
||||
deno --version && bun -v && go version && python3 --version
|
||||
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,private --all -- --nocapture
|
||||
|
||||
140
.github/workflows/build-publish-rh8-image.yml
vendored
Normal file
140
.github/workflows/build-publish-rh8-image.yml
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Build and publish windmill for RHEL8
|
||||
on: workflow_dispatch
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build_ee:
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: windmill-labs/windmill-ee-private
|
||||
path: ./windmill-ee-private
|
||||
ref: ${{ env.ee_repo_ref }}
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-rhel8
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=sha
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Substitute EE code
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Copy RHEL8 Dockerfile
|
||||
run: |
|
||||
cp ./docker/RHEL8/Dockerfile ./Dockerfile
|
||||
|
||||
- name: Build and push publicly ee amd64
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}-amd64
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}-amd64
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
- name: Build and push publicly ee arm64
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}-arm64
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}-arm64
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract-ee-amd64
|
||||
with:
|
||||
image: ${{ steps.meta-ee-public.outputs.tags}}-amd64
|
||||
path: "/windmill/target/release/windmill"
|
||||
|
||||
- uses: shrink/actions-docker-extract@v3
|
||||
id: extract-duckdb-ffi-internal
|
||||
with:
|
||||
image: ${{ steps.meta-ee-public.outputs.tags}}-amd64
|
||||
path: "/usr/src/app/libwindmill_duckdb_ffi_internal.so"
|
||||
|
||||
# - uses: shrink/actions-docker-extract@v3
|
||||
# id: extract-ee-arm64
|
||||
# with:
|
||||
# image: ${{ steps.meta-ee-public.outputs.tags}}-arm64
|
||||
# path: "/windmill/target/release/windmill"
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
mv "${{ steps.extract-ee-amd64.outputs.destination }}/windmill" "${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8"
|
||||
# mv "${{ steps.extract-ee-arm64.outputs.destination }}/windmill" "${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: RHEL8-amd64 build
|
||||
path: ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: RHEL8-amd64 dynamic libraries build
|
||||
path: ${{ steps.extract-duckdb-ffi-internal.outputs.destination }}/libwindmill_duckdb_ffi_internal.so
|
||||
|
||||
# - uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: RHEL8-arm64 build
|
||||
# path:
|
||||
# ${{ steps.extract-ee-arm64.outputs.destination
|
||||
# }}/windmill-ee-arm64-rhel8
|
||||
|
||||
# - name: Attach binary to release
|
||||
# uses: softprops/action-gh-release@v2
|
||||
# if: startsWith(github.ref, 'refs/tags/')
|
||||
# with:
|
||||
# files: |
|
||||
# ${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel8
|
||||
# ${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel8
|
||||
3
.github/workflows/git-commands.yaml
vendored
3
.github/workflows/git-commands.yaml
vendored
@@ -40,11 +40,12 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ steps.app.outputs.token }}
|
||||
script: |
|
||||
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'Starting sqlx update...'
|
||||
body: `Starting sqlx update...\n\n[View workflow run](${runUrl})`
|
||||
})
|
||||
|
||||
- name: Checkout repository
|
||||
|
||||
32
.github/workflows/weekly-pr-summary.yml
vendored
32
.github/workflows/weekly-pr-summary.yml
vendored
@@ -49,14 +49,14 @@ jobs:
|
||||
- **Bug Fixes**: PRs with titles starting with "fix:", "bug:", or containing "fix", "resolve", "patch"
|
||||
- **Other**: All remaining PRs (improvements, refactors, docs, chores, etc.)
|
||||
|
||||
4. **Gather Details**: For each merged PR, include:
|
||||
4. **Gather Details**: For each feature and bug fix merged PR, include:
|
||||
- Full PR title (NO truncation, NO links)
|
||||
- Author (extract login from author.login in JSON)
|
||||
- Brief summary: Use `gh pr view <number> --json body` to get PR description, then extract first paragraph or key points (1-2 sentences max)
|
||||
|
||||
5. **Character Limit Enforcement**:
|
||||
- The final summary MUST be under 6000 characters
|
||||
- If the summary exceeds 6000 characters, truncate PR descriptions (NOT titles) and add at the end: "_and X more PRs_" where X is the count of omitted PRs
|
||||
- The final summary MUST be under 5000 characters
|
||||
- If the summary exceeds 5000 characters, truncate PR descriptions (NOT titles) and add at the end: "_and X more PRs_" where X is the count of omitted PRs
|
||||
|
||||
6. **Save Summary to Markdown File**: Write the summary to a file for webhook delivery:
|
||||
- Save the complete formatted markdown to: `summary.md`
|
||||
@@ -65,23 +65,19 @@ jobs:
|
||||
## Output Format:
|
||||
|
||||
```markdown
|
||||
#### 📈 Weekly overview
|
||||
### 📈 Weekly overview
|
||||
- **Total merged**: X
|
||||
- **Features**: Y
|
||||
- **Bug Fixes**: Z
|
||||
- **Other**: W
|
||||
|
||||
#### ✨ Features (Y)
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
### ✨ Features (Y)
|
||||
- **[Full PR Title]** by @username - [brief impact description]
|
||||
- **[Full PR Title]** by @username - [brief impact description]
|
||||
|
||||
#### 🐛 Bug Fixes (Z)
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
|
||||
#### 🔧 Other (W)
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
• **[Full PR Title]** by @username - [brief impact description]
|
||||
### 🐛 Bug Fixes (Z)
|
||||
- **[Full PR Title]** by @username - [brief impact description]
|
||||
- **[Full PR Title]** by @username - [brief impact description]
|
||||
|
||||
_and X more PRs_
|
||||
```
|
||||
@@ -89,9 +85,9 @@ jobs:
|
||||
## Important Notes:
|
||||
- **CRITICAL**: ONLY include PRs with state "merged" from the last 7 days
|
||||
- **CRITICAL**: EXCLUDE all PRs with titles starting with "chore: release" or "chore(release)"
|
||||
- **CRITICAL**: Total character count MUST be under 6000 characters
|
||||
- Only use #### markdown headers for major sections and emoji indicators
|
||||
- Use bullet points (•) for individual PR entries - more compact than paragraphs
|
||||
- **CRITICAL**: Total character count MUST be under 5000 characters
|
||||
- Count the number of "Other" PRs but do not include a section for them in the output
|
||||
- Only use ### markdown headers for major sections and emoji indicators
|
||||
- NO links to PRs
|
||||
- NO merged date in output
|
||||
- NEVER truncate PR titles - show full titles
|
||||
@@ -100,7 +96,7 @@ jobs:
|
||||
- If a PR has no description, write "(No description provided)"
|
||||
- Extract meaningful summary from PR body - look for the first paragraph or key bullet points
|
||||
- Parse JSON responses carefully using `jq` or similar tools
|
||||
- If summary exceeds 6000 chars, shorten PR descriptions and add "_and X more PRs_" at the end
|
||||
- If summary exceeds 5000 chars, shorten PR descriptions and add "_and X more PRs_" at the end
|
||||
- Count PRs in each category and display in both overview and section headers
|
||||
|
||||
## Saving the Markdown Output:
|
||||
|
||||
219
CHANGELOG.md
219
CHANGELOG.md
@@ -1,5 +1,224 @@
|
||||
# Changelog
|
||||
|
||||
## [1.580.0](https://github.com/windmill-labs/windmill/compare/v1.579.2...v1.580.0) (2025-11-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **aichat:** use responses api for openai models ([#7163](https://github.com/windmill-labs/windmill/issues/7163)) ([5c79a35](https://github.com/windmill-labs/windmill/commit/5c79a35306855143428d0725519578aea0a746fd))
|
||||
* disabling/enabling email triggers ([#7171](https://github.com/windmill-labs/windmill/issues/7171)) ([8ae266b](https://github.com/windmill-labs/windmill/commit/8ae266b6a9ced16e1b7416cfc8bea5fe7a7af042))
|
||||
* **security:** unshare pid of worker job process ([#7106](https://github.com/windmill-labs/windmill/issues/7106)) ([5aa251a](https://github.com/windmill-labs/windmill/commit/5aa251a2d276cc9d27bf104f8e4f724ea6a28231))
|
||||
* support secondary promotion repos in git sync settings ([#7173](https://github.com/windmill-labs/windmill/issues/7173)) ([5548221](https://github.com/windmill-labs/windmill/commit/55482210921fe2eb0fd158abd4f7369495f2dfd7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change uv tool dir from /root to /usr/local/uv ([c3e59fe](https://github.com/windmill-labs/windmill/commit/c3e59fe064fc3b9d4c05958eea54601ff3410899))
|
||||
* improve delete to handle ai chat ([f371fbe](https://github.com/windmill-labs/windmill/commit/f371fbeb9bb0946bd29a6413ee7ede75dedda5d9))
|
||||
* support IRSA for duckdb s3 proxy ([2058f27](https://github.com/windmill-labs/windmill/commit/2058f27e03468d45813f340b8563f935ca2142f4))
|
||||
|
||||
## [1.579.2](https://github.com/windmill-labs/windmill/compare/v1.579.1...v1.579.2) (2025-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ducklake manager table explorer issue ([d08c091](https://github.com/windmill-labs/windmill/commit/d08c0916f72a67f01e0c4475f03f9d1d33c10905))
|
||||
|
||||
## [1.579.1](https://github.com/windmill-labs/windmill/compare/v1.579.0...v1.579.1) (2025-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix s3 object download frontend freezes ([09a6e1f](https://github.com/windmill-labs/windmill/commit/09a6e1feaa79ce3f8548f8090fddbf46abb08b18))
|
||||
|
||||
## [1.579.0](https://github.com/windmill-labs/windmill/compare/v1.578.0...v1.579.0) (2025-11-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ai:** handle aws bedrock as provider ([#7155](https://github.com/windmill-labs/windmill/issues/7155)) ([79ac631](https://github.com/windmill-labs/windmill/commit/79ac6312e87afa3646bddc0f7e66fc4367dbff7c))
|
||||
* **mcp:** granular token scopes for scripts, flows, and endpoints ([#7130](https://github.com/windmill-labs/windmill/issues/7130)) ([88d04b9](https://github.com/windmill-labs/windmill/commit/88d04b9cbeee98f3256b78e9d34beb930cd729ec))
|
||||
* rhel8 + fix rhel9 ([#7165](https://github.com/windmill-labs/windmill/issues/7165)) ([499d7d4](https://github.com/windmill-labs/windmill/commit/499d7d4098758726a8cb2bf3e4837927b8fd70a4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** worker count in latest worker usage ([#7160](https://github.com/windmill-labs/windmill/issues/7160)) ([b87d2cc](https://github.com/windmill-labs/windmill/commit/b87d2cc64cb54b602ee599fcde7f0fd3c8931550))
|
||||
* fix custom email triggers enabled ([#7164](https://github.com/windmill-labs/windmill/issues/7164)) ([90b5569](https://github.com/windmill-labs/windmill/commit/90b5569c911f9025b0e6b5318f57705efbd9bd17))
|
||||
|
||||
## [1.578.0](https://github.com/windmill-labs/windmill/compare/v1.577.0...v1.578.0) (2025-11-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support to run windows binary as service ([#7153](https://github.com/windmill-labs/windmill/issues/7153)) ([ceeff5f](https://github.com/windmill-labs/windmill/commit/ceeff5f76c69d98319bb3fb7f7779b6046478d6b))
|
||||
|
||||
## [1.577.0](https://github.com/windmill-labs/windmill/compare/v1.576.3...v1.577.0) (2025-11-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for validateset in pwsh ([#7158](https://github.com/windmill-labs/windmill/issues/7158)) ([b66e038](https://github.com/windmill-labs/windmill/commit/b66e038a0f8b6bffe157a83671c8e692c1441f23))
|
||||
* allow http trigger to be disabled ([#6976](https://github.com/windmill-labs/windmill/issues/6976)) ([09082de](https://github.com/windmill-labs/windmill/commit/09082de53971d0d2f2a6308bc8ee573458a3b913))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* create app_themes/groups/components only when needed ([cf5d58e](https://github.com/windmill-labs/windmill/commit/cf5d58ea43cef6add3da2aa1e24efc83be6df3b9))
|
||||
* fix parse_postgres_uri not decoding password ([#7157](https://github.com/windmill-labs/windmill/issues/7157)) ([2cae72c](https://github.com/windmill-labs/windmill/commit/2cae72c9db6bd08689e1672be6dda32f6af831fb))
|
||||
|
||||
## [1.576.3](https://github.com/windmill-labs/windmill/compare/v1.576.2...v1.576.3) (2025-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle better alias types in duckdb ([2c04e04](https://github.com/windmill-labs/windmill/commit/2c04e04bf0e3272c89f321392158888d02a1191b))
|
||||
|
||||
## [1.576.2](https://github.com/windmill-labs/windmill/compare/v1.576.1...v1.576.2) (2025-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* temporary fix for duckdb type_aliases causing issues ([#7148](https://github.com/windmill-labs/windmill/issues/7148)) ([6426ebf](https://github.com/windmill-labs/windmill/commit/6426ebf8cb713443904065064b6a07eb1db0761a))
|
||||
|
||||
## [1.576.1](https://github.com/windmill-labs/windmill/compare/v1.576.0...v1.576.1) (2025-11-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* DuckDB FFI crash fix ([#7145](https://github.com/windmill-labs/windmill/issues/7145)) ([d3fc459](https://github.com/windmill-labs/windmill/commit/d3fc459b407682bf588236236916363d94f3e1ff))
|
||||
|
||||
## [1.576.0](https://github.com/windmill-labs/windmill/compare/v1.575.4...v1.576.0) (2025-11-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for switch and attributes in pwsh params ([#7143](https://github.com/windmill-labs/windmill/issues/7143)) ([c16bef8](https://github.com/windmill-labs/windmill/commit/c16bef8f296645ff873f9d8d28e3dcb50a65e304))
|
||||
* **ai:** handle aws bedrock as provider ([#7131](https://github.com/windmill-labs/windmill/issues/7131)) ([30eb9aa](https://github.com/windmill-labs/windmill/commit/30eb9aae25eeb563ad119ef93f3ff1ab17c66d75))
|
||||
* webhook by flow version ([#7062](https://github.com/windmill-labs/windmill/issues/7062)) ([09cdfb4](https://github.com/windmill-labs/windmill/commit/09cdfb4556748903dc5bbf53ef3356ac97c57d90))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use proper TLS connector for DuckLake instance catalog setup ([#7138](https://github.com/windmill-labs/windmill/issues/7138)) ([cf36fe3](https://github.com/windmill-labs/windmill/commit/cf36fe3bb1beec80fa84dc342a8a38cc7369bc4d))
|
||||
|
||||
## [1.575.4](https://github.com/windmill-labs/windmill/compare/v1.575.3...v1.575.4) (2025-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix cancel job selection ([d4340b8](https://github.com/windmill-labs/windmill/commit/d4340b83ac94a9dd9febdcfa11288f35c0a57799))
|
||||
|
||||
## [1.575.3](https://github.com/windmill-labs/windmill/compare/v1.575.2...v1.575.3) (2025-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix cancel job selection ([fbab86c](https://github.com/windmill-labs/windmill/commit/fbab86cc43dae17727a3aa99abc43a57c6b59b57))
|
||||
|
||||
## [1.575.2](https://github.com/windmill-labs/windmill/compare/v1.575.1...v1.575.2) (2025-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix cancel job selection ([1c0f80d](https://github.com/windmill-labs/windmill/commit/1c0f80dc5594146b0ec18caf3b49c036695ce759))
|
||||
|
||||
## [1.575.1](https://github.com/windmill-labs/windmill/compare/v1.575.0...v1.575.1) (2025-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix flow buttons clipped in Safari ([#7126](https://github.com/windmill-labs/windmill/issues/7126)) ([77316cb](https://github.com/windmill-labs/windmill/commit/77316cbd0e3ee81fccee9680541f9b2346c8a209))
|
||||
* handle string in input transform strings ([2ee17ae](https://github.com/windmill-labs/windmill/commit/2ee17aea8b29d99c5d2bb609b2becad354835de7))
|
||||
* send alternatives on timeout toast ([#6920](https://github.com/windmill-labs/windmill/issues/6920)) ([6fac896](https://github.com/windmill-labs/windmill/commit/6fac8963909fb40726d85f5e897398d2a691edb0))
|
||||
|
||||
## [1.575.0](https://github.com/windmill-labs/windmill/compare/v1.574.3...v1.575.0) (2025-11-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* SQL result_collection flag ([#7041](https://github.com/windmill-labs/windmill/issues/7041)) ([14e41ba](https://github.com/windmill-labs/windmill/commit/14e41babfe6321abb22081b1b3601e9009451257))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow --yes in fork command ([#7122](https://github.com/windmill-labs/windmill/issues/7122)) ([1280617](https://github.com/windmill-labs/windmill/commit/1280617a283ef7bcf53c17d833f1413ffe69dbf8))
|
||||
* **backend:** scim email filter ([#7120](https://github.com/windmill-labs/windmill/issues/7120)) ([7a8fd86](https://github.com/windmill-labs/windmill/commit/7a8fd865e289522fd2d5eaaa9af471f01193a03d))
|
||||
* **flow chat:** fix chat in edit mode + cleaner code ([#7118](https://github.com/windmill-labs/windmill/issues/7118)) ([9791eb4](https://github.com/windmill-labs/windmill/commit/9791eb4568aa9548174941f1bbfb6a338c3bef00))
|
||||
|
||||
## [1.574.3](https://github.com/windmill-labs/windmill/compare/v1.574.2...v1.574.3) (2025-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** add sslmode parameter to DuckLake PostgreSQL connections ([ea5b3e6](https://github.com/windmill-labs/windmill/commit/ea5b3e6b9269a0c631c7601313ab97c4bbdc2137))
|
||||
* use teams internal_id rather than id ([#7113](https://github.com/windmill-labs/windmill/issues/7113)) ([25c36c1](https://github.com/windmill-labs/windmill/commit/25c36c1e0a6cfc56e079ff5a18980ea67403d4f9))
|
||||
|
||||
## [1.574.2](https://github.com/windmill-labs/windmill/compare/v1.574.1...v1.574.2) (2025-11-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** improve pwsh param block parsing ([#7096](https://github.com/windmill-labs/windmill/issues/7096)) ([51cba95](https://github.com/windmill-labs/windmill/commit/51cba95d3961ec637fd5060556f0e7a8a8bdbb88))
|
||||
* **backend:** improve sse parsing ([#7100](https://github.com/windmill-labs/windmill/issues/7100)) ([2e245ac](https://github.com/windmill-labs/windmill/commit/2e245accc5f641f64a2cd28fc181e6a806c407e2))
|
||||
* fix multiselect in list for apps ([9e4882c](https://github.com/windmill-labs/windmill/commit/9e4882c0a919303480f5a431db3c05b1d5855d37))
|
||||
* make ai chat works with unicode messages ([2d54dfb](https://github.com/windmill-labs/windmill/commit/2d54dfbf05f21faa14655b7124daa291a9d8d66d))
|
||||
* preproccessor ui and expanding preprocessor support ([#6872](https://github.com/windmill-labs/windmill/issues/6872)) ([fc5034e](https://github.com/windmill-labs/windmill/commit/fc5034e94d513bab2feb0ebb2aff822bbb42e4aa))
|
||||
* proper error handling in pulled job preprocessor ([#7098](https://github.com/windmill-labs/windmill/issues/7098)) ([84992cd](https://github.com/windmill-labs/windmill/commit/84992cd8ffa94e009aeb6b25db224a3001581e15))
|
||||
|
||||
## [1.574.1](https://github.com/windmill-labs/windmill/compare/v1.574.0...v1.574.1) (2025-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make get_logs work even for partial flow jobs ([d6421c2](https://github.com/windmill-labs/windmill/commit/d6421c2ea79993ef7815c50cf035d3b3425e271e))
|
||||
|
||||
## [1.574.0](https://github.com/windmill-labs/windmill/compare/v1.573.5...v1.574.0) (2025-11-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* env var in flow ([#6852](https://github.com/windmill-labs/windmill/issues/6852)) ([c59183f](https://github.com/windmill-labs/windmill/commit/c59183f5c39f853d9679c00dd5aa755ef171d735))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** add automatic handler of .node files for codebase bundler ([62ffe9f](https://github.com/windmill-labs/windmill/commit/62ffe9ffce6d070c81794e8483ab83b431aee9ea))
|
||||
* teams selector svelte5 ([#7087](https://github.com/windmill-labs/windmill/issues/7087)) ([6045f0c](https://github.com/windmill-labs/windmill/commit/6045f0c40654a88e93be688bcdfab874cfc0b267))
|
||||
|
||||
## [1.573.5](https://github.com/windmill-labs/windmill/compare/v1.573.4...v1.573.5) (2025-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dep jobs improvements ([#7081](https://github.com/windmill-labs/windmill/issues/7081)) ([ad43680](https://github.com/windmill-labs/windmill/commit/ad43680bb979f80db3bdb182ccb5bc61ca385ceb))
|
||||
* modal action in App AgGrid ([#7085](https://github.com/windmill-labs/windmill/issues/7085)) ([b07c35f](https://github.com/windmill-labs/windmill/commit/b07c35fe1c9dbc4955ce47766ef92bd5cfdac4c8))
|
||||
|
||||
## [1.573.4](https://github.com/windmill-labs/windmill/compare/v1.573.3...v1.573.4) (2025-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bun:** support relative imports without .ts but starting with /u/ or /f/ ([9e3ea76](https://github.com/windmill-labs/windmill/commit/9e3ea7657568d6561abcdfd23cc8a210432e4050))
|
||||
* create git branch right before creating the workspace fork to catch errors and have a coherent fork point ([#7073](https://github.com/windmill-labs/windmill/issues/7073)) ([192fecc](https://github.com/windmill-labs/windmill/commit/192fecc86f54cf2a69ec7dddc466139b40933ac9))
|
||||
* improve priority for child jobs ran from api ([5f7e0b7](https://github.com/windmill-labs/windmill/commit/5f7e0b7244e27533d9a8d1551e8affeb17eb94c0))
|
||||
* make it impossible to have non linear script history even in edge condition ([3bda6da](https://github.com/windmill-labs/windmill/commit/3bda6da1683589180cad0780dd87b391a04851f8))
|
||||
|
||||
## [1.573.3](https://github.com/windmill-labs/windmill/compare/v1.573.2...v1.573.3) (2025-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* job streaming improvement after compaction ([f16231d](https://github.com/windmill-labs/windmill/commit/f16231d7c9837efa3873034498abc9bfa87575d5))
|
||||
|
||||
## [1.573.2](https://github.com/windmill-labs/windmill/compare/v1.573.1...v1.573.2) (2025-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* authentik frontend baseurl field empty when loaded from db ([#7065](https://github.com/windmill-labs/windmill/issues/7065)) ([8cb8650](https://github.com/windmill-labs/windmill/commit/8cb8650460856a1975c6eee429a2771f784a1555))
|
||||
* oidc token issue ([75e056b](https://github.com/windmill-labs/windmill/commit/75e056bbce7e00334a36175eed00eb8eba332fbc))
|
||||
|
||||
## [1.573.1](https://github.com/windmill-labs/windmill/compare/v1.573.0...v1.573.1) (2025-11-05)
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +114,10 @@ ARG WITH_GIT=true
|
||||
ARG LATEST_STABLE_PY=3.11.10
|
||||
ENV UV_PYTHON_INSTALL_DIR=/tmp/windmill/cache/py_runtime
|
||||
ENV UV_PYTHON_PREFERENCE=only-managed
|
||||
|
||||
RUN mkdir -p /usr/local/uv
|
||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||
ENV UV_TOOL_DIR=/usr/local/uv
|
||||
|
||||
ENV PATH /usr/local/bin:/root/.local/bin:$PATH
|
||||
|
||||
|
||||
@@ -13,4 +13,7 @@ rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
"-C", "link-args=-Wl,-rpath,$ORIGIN/"
|
||||
]
|
||||
]
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "045b26db0cefe6eaac0e572661d984ff5ce7086ac511e8647e2024d9dbe0af56"
|
||||
}
|
||||
14
backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json
generated
Normal file
14
backend/.sqlx/query-08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM flow_conversation WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "08574e8e5dc165041750880fb02e7ffea83ae94a670b598b6dada0b3d0914629"
|
||||
}
|
||||
25
backend/.sqlx/query-0c0f3909b80c35210fc64c685905308621f9135c2c45a2fa0531ea750387da1f.json
generated
Normal file
25
backend/.sqlx/query-0c0f3909b80c35210fc64c685905308621f9135c2c45a2fa0531ea750387da1f.json
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n CASE \n WHEN flow_version.id IS NOT NULL THEN\n (flow_version.value -> 'flow_env' -> $3) #> $4\n ELSE\n (root_job.raw_flow -> 'flow_env' -> $3) #> $4\n END AS \"flow_env: sqlx::types::Json<Box<RawValue>>\"\n FROM \n v2_job current_job\n JOIN \n v2_job root_job ON root_job.id = COALESCE(current_job.root_job, current_job.flow_innermost_root_job, current_job.parent_job, current_job.id)\n AND root_job.workspace_id = current_job.workspace_id\n LEFT JOIN\n flow_version ON flow_version.id = root_job.runnable_id\n AND flow_version.path = root_job.runnable_path\n AND flow_version.workspace_id = root_job.workspace_id\n WHERE \n current_job.id = $1 AND \n current_job.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "flow_env: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "0c0f3909b80c35210fc64c685905308621f9135c2c45a2fa0531ea750387da1f"
|
||||
}
|
||||
23
backend/.sqlx/query-1fc43faf3a7ea15be82b1666273836901cc6ba17c7d5e194b50b9204f264de27.json
generated
Normal file
23
backend/.sqlx/query-1fc43faf3a7ea15be82b1666273836901cc6ba17c7d5e194b50b9204f264de27.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1 FROM script WHERE hash = $1 AND workspace_id = $2 FOR UPDATE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1fc43faf3a7ea15be82b1666273836901cc6ba17c7d5e194b50b9204f264de27"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT j.id AS \"id!\" FROM v2_job j WHERE j.id = ANY($1) AND j.trigger_kind != 'schedule'::job_trigger_kind AND ($2::text[] IS NULL OR j.tag = ANY($2))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "24d302b8215d49a289bedd14a5791e9366d1f6d3d2aa485e0f50c6f2d85693dd"
|
||||
}
|
||||
17
backend/.sqlx/query-38baa6cf7d1c2532d38486a01e6d27ac1a58540a51e97dec5613e9bc0791e890.json
generated
Normal file
17
backend/.sqlx/query-38baa6cf7d1c2532d38486a01e6d27ac1a58540a51e97dec5613e9bc0791e890.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO ai_agent_memory (workspace_id, conversation_id, step_id, messages, created_at, updated_at)\n VALUES ($1, $2, $3, $4, NOW(), NOW())\n ON CONFLICT (workspace_id, conversation_id, step_id)\n DO UPDATE SET\n messages = EXCLUDED.messages,\n updated_at = NOW()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "38baa6cf7d1c2532d38486a01e6d27ac1a58540a51e97dec5613e9bc0791e890"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n SELECT app_id, value, created_by, raw_app\n FROM app_version WHERE id = $1\n RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3d38720e807b379645d8f3ab61c6a968143d42c3014152608f7d1b252cd8085c"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "42baae3c69367bbb21771a9711c311afe67d339ca022ba61c2767c004b038ef0"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE job_logs SET logs = $1, log_offset = $2, \n log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3) \n WHERE workspace_id = $4 AND job_id = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "528cdbb75f1c5135170a58fce3fda464be138272487639d0ffbbbe6961ec5c37"
|
||||
}
|
||||
14
backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json
generated
Normal file
14
backend/.sqlx/query-56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM ai_agent_memory WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "56d3dccce81c652d6ab3d4ba69de89c4a5cb1fbc9a8b73e6b8ba4d6e74088352"
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT j.created_by AS \"created_by!\", CONCAT(coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index\n FROM v2_job j\n LEFT JOIN job_logs ON job_logs.job_id = j.id\n WHERE j.id = $1 AND j.workspace_id = $2 AND ($3::text[] IS NULL OR j.tag = ANY($3))",
|
||||
"query": "SELECT j.created_by AS \"created_by\", coalesce(job_logs.logs, '') as logs, COALESCE(job_logs.log_offset, 0) as log_offset, job_logs.log_file_index\n FROM v2_job j\n LEFT JOIN job_logs ON job_logs.job_id = j.id\n WHERE j.id = $1 AND j.workspace_id = $2 AND ($3::text[] IS NULL OR j.tag = ANY($3))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_by!",
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
@@ -34,9 +34,9 @@
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "35061719d01929a7146c80de4b637abdad3198d3340ec7c04ed671baff0a4d0b"
|
||||
"hash": "5e7cadffbee74b11e224b60322b102b9899b4a97b8e557692c0085a9b472b8a7"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n ",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n AND enabled IS TRUE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -66,5 +66,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "668edc2f84eccf5db6b8daa3ed97e25e46a37c50a9d1a6eca5a277fb58a2e7b4"
|
||||
"hash": "628d303956d55d22e5ac64496b66010d4476d5c222184f09e5a189975a37b941"
|
||||
}
|
||||
14
backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json
generated
Normal file
14
backend/.sqlx/query-6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6525e65ffe66643fed4db83aa81b525fc3ec54fdbca5d12647c1da88c9983b10"
|
||||
}
|
||||
24
backend/.sqlx/query-6a1005b8ae5326c5d5955534e9228b6bd524253971ae09cbe100c0d6d42e6cdc.json
generated
Normal file
24
backend/.sqlx/query-6a1005b8ae5326c5d5955534e9228b6bd524253971ae09cbe100c0d6d42e6cdc.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT messages FROM ai_agent_memory\n WHERE workspace_id = $1 AND conversation_id = $2 AND step_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "messages",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6a1005b8ae5326c5d5955534e9228b6bd524253971ae09cbe100c0d6d42e6cdc"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11\n )\n ",
|
||||
"query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n enabled\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -15,10 +15,11 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb"
|
||||
"Jsonb",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d328d00e5393b0e8d2c4b3674221fabbae580fc404e0d4481fd8d7fb51272c94"
|
||||
"hash": "6c568509908c1833d9e6f58f739b8a186fc91f94d4fcc40eeb9914084e4ef60c"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id!: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang!: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind!: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
|
||||
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "runnable_id!: ScriptHash",
|
||||
"name": "runnable_id: ScriptHash",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "script_lang!: ScriptLang",
|
||||
"name": "script_lang: ScriptLang",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
@@ -133,7 +133,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "trigger_kind!: JobTriggerKind",
|
||||
"name": "trigger_kind: JobTriggerKind",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "job_trigger_kind",
|
||||
@@ -212,5 +212,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "a001b4254e0f1ba8a87776e32f9506dce6eb4af3fb32e6a4c6e76084873a539a"
|
||||
"hash": "6cc922a5bbd348c938a9d1431aaa0f24f078ea814b429d44403aca1e5002e750"
|
||||
}
|
||||
16
backend/.sqlx/query-6ec1e81e1698c754ca8a660cf6a3ae66e75b487901befd647839dd2bc2233bf4.json
generated
Normal file
16
backend/.sqlx/query-6ec1e81e1698c754ca8a660cf6a3ae66e75b487901befd647839dd2bc2233bf4.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE job_logs SET logs = $1 WHERE workspace_id = $2 AND job_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6ec1e81e1698c754ca8a660cf6a3ae66e75b487901befd647839dd2bc2233bf4"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69"
|
||||
"hash": "6f49a47b0b77720b0e8a2ddbd46e45e414dd646c912387ee63609d9f5611690f"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9 WHERE worker = $5",
|
||||
"query": "UPDATE worker_ping SET ping_at = now(), current_job_id = $1, current_job_workspace_id = $2, memory_usage = $3, wm_memory_usage = $4,\n occupancy_rate = $6, occupancy_rate_15s = $7, occupancy_rate_5m = $8, occupancy_rate_30m = $9, job_isolation = $10 WHERE worker = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,10 +13,11 @@
|
||||
"Float4",
|
||||
"Float4",
|
||||
"Float4",
|
||||
"Float4"
|
||||
"Float4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "506066203c49424e9944eb3948dc1657d3d796e6233e9f0ec925879c705d4773"
|
||||
"hash": "76a7ad0588afcb4e9f0b876c50e203c36be56b2b2b08ca787417a9822ef56f64"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id, \n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id,\n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage, job_isolation\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -97,6 +97,11 @@
|
||||
"ordinal": 18,
|
||||
"name": "wm_memory_usage",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "job_isolation",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -126,8 +131,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "6a497334c98bfaf70be44fced572a1cc0dde4141aa4c5002765a95432d0101ab"
|
||||
"hash": "771a858a4b7ca41b6787e61f5a4a5c9c4d48fd213852e2f997cd4b2420580d30"
|
||||
}
|
||||
14
backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json
generated
Normal file
14
backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO unique_ext_jwt_token (jwt_hash, last_used_at)\n VALUES ($1, NOW())\n ON CONFLICT (jwt_hash)\n DO UPDATE SET last_used_at = NOW()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b"
|
||||
}
|
||||
23
backend/.sqlx/query-78f5684086a8c28a602c583389e974fffd7f74b10618b1467426623973c10131.json
generated
Normal file
23
backend/.sqlx/query-78f5684086a8c28a602c583389e974fffd7f74b10618b1467426623973c10131.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT j.id AS \"id!\" FROM v2_job j LEFT JOIN v2_job_queue q USING (id) WHERE j.id = ANY($1) AND j.trigger_kind IS DISTINCT FROM 'schedule'::job_trigger_kind AND ($2::text[] IS NULL OR j.tag = ANY($2))",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "78f5684086a8c28a602c583389e974fffd7f74b10618b1467426623973c10131"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(id) FROM v2_job WHERE permissioned_as_email = $1",
|
||||
"query": "SELECT COUNT(j.id) FROM v2_job_queue q JOIN v2_job j USING (id) WHERE j.permissioned_as_email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -18,5 +18,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1368ccd2c15a75690041a6c87d4a2849fe6bc668654ffcbfbc22a02027280739"
|
||||
"hash": "7a4e79bcae7c7d69e4bb71c8540b2adb9ca8bd15098038e3ccf8b16164944951"
|
||||
}
|
||||
23
backend/.sqlx/query-7cab8475996f0abd7ca7888b460bd3faf3bef6e34200c258e3704981817c241e.json
generated
Normal file
23
backend/.sqlx/query-7cab8475996f0abd7ca7888b460bd3faf3bef6e34200c258e3704981817c241e.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7cab8475996f0abd7ca7888b460bd3faf3bef6e34200c258e3704981817c241e"
|
||||
}
|
||||
22
backend/.sqlx/query-83232f2db5eb1b6fef744998e60420ef920d472286cf4c1f78452446a4bcb604.json
generated
Normal file
22
backend/.sqlx/query-83232f2db5eb1b6fef744998e60420ef920d472286cf4c1f78452446a4bcb604.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO app_version\n (app_id, value, created_by, raw_app)\n SELECT app_id, value, created_by, raw_app\n FROM app_version WHERE id = $1\n RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "83232f2db5eb1b6fef744998e60420ef920d472286cf4c1f78452446a4bcb604"
|
||||
}
|
||||
14
backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json
generated
Normal file
14
backend/.sqlx/query-88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "88d529fd26d6ecba7640c74daccb5cb0c7146c11adae8c287c0c9e71ac68f7a7"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed"
|
||||
"hash": "8e750d4b3af9b5844c11b1b92741f2ee9d2d3412d4a2c96c6ccc87ec1c382384"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19, $20, $21, $22\n )\n ",
|
||||
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n enabled,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -17,6 +17,7 @@
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "request_type",
|
||||
@@ -69,5 +70,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1f6b773ce34fe51d03d6f9a2345481629c62453eebbb08f82dd2da23389bc117"
|
||||
"hash": "94e2e899fc0a5134c29217feda1f0530d03548cc5c884c83d7b85d068e738b8e"
|
||||
}
|
||||
18
backend/.sqlx/query-a0af7dc507778ab23d5b1615e2a03278de05f4cd90fb1c31d7281f8681c7f85d.json
generated
Normal file
18
backend/.sqlx/query-a0af7dc507778ab23d5b1615e2a03278de05f4cd90fb1c31d7281f8681c7f85d.json
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE job_logs SET logs = $1, log_offset = $2,\n log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3)\n WHERE workspace_id = $4 AND job_id = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a0af7dc507778ab23d5b1615e2a03278de05f4cd90fb1c31d7281f8681c7f85d"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a6a973dcd92d2e40fd9a1c1be42052fcd350bd47ee4f63832448b6e6f0f472f0"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8\n WHERE \n workspace_id = $9 AND path = $10\n ",
|
||||
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n enabled = $9\n WHERE \n workspace_id = $10 AND path = $11\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,11 +13,12 @@
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "acb094aef60bba9083087264d65034fce38417099f15e8312be72a386f10bc1f"
|
||||
"hash": "a704283ff62ac1cd6db489ca3f84d6994d75c6b9e5f61daf54d298a2fdcfd9af"
|
||||
}
|
||||
66
backend/.sqlx/query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json
generated
Normal file
66
backend/.sqlx/query-a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26.json
generated
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return, \n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, \n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, \n flow.tag, \n flow.dedicated_worker, \n flow.on_behalf_of_email, \n flow.edited_by\n FROM \n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE \n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "has_preprocessor",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "dedicated_worker",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a7468e9054beed88636786c5495ac3b9d9a6086ae6212ad9237b39a0346d7d26"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n http_method = $7,\n static_asset_config = $8,\n edited_by = $9,\n email = $10,\n request_type = $11,\n authentication_method = $12,\n summary = $13,\n description = $14,\n edited_at = now(),\n is_static_website = $15,\n error_handler_path = $16,\n error_handler_args = $17,\n retry = $18\n WHERE\n workspace_id = $19 AND\n path = $20\n ",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n enabled = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n email = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,6 +11,7 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "http_method",
|
||||
@@ -67,5 +68,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4c64bd0e364f536597db83161b5a27ff58b5ec7148bc94807423593bcfa27b90"
|
||||
"hash": "ada1a14b4f25e41742df153b07e8bdf5b5feac9c6fa9de38bd24104bac8539d2"
|
||||
}
|
||||
23
backend/.sqlx/query-b82f97f2a858e20b1fb6d9b0b3eab5bab2bafa91689eac5073c1678c338afb27.json
generated
Normal file
23
backend/.sqlx/query-b82f97f2a858e20b1fb6d9b0b3eab5bab2bafa91689eac5073c1678c338afb27.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n path \n FROM \n flow_version \n WHERE \n id = $1 AND \n workspace_id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b82f97f2a858e20b1fb6d9b0b3eab5bab2bafa91689eac5073c1678c338afb27"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n http_method = $10,\n static_asset_config = $11,\n edited_by = $12,\n email = $13,\n request_type = $14,\n authentication_method = $15,\n summary = $16,\n description = $17,\n edited_at = now(),\n is_static_website = $18,\n error_handler_path = $19,\n error_handler_args = $20,\n retry = $21\n WHERE\n workspace_id = $22 AND\n path = $23\n ",
|
||||
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n enabled = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n email = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,6 +14,7 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "http_method",
|
||||
@@ -70,5 +71,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "465144ea7e2930203618d9814a3e20c77b4363cf9e7c655d395f3fe40c247f61"
|
||||
"hash": "b8bcf1fef244395dad802174dfc9bff45ff6f9cd1298eae5de40218cbab2eb96"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1\n ",
|
||||
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n enabled is TRUE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -158,5 +158,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1301f873a829db137573b8b39449f6160f2adf44f864f26a99b8eab5818fbd50"
|
||||
"hash": "bbc96ae911d4ca0330582340e65c10e008e507610f095ff3936865101c9ba346"
|
||||
}
|
||||
22
backend/.sqlx/query-be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560.json
generated
Normal file
22
backend/.sqlx/query-be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "installation_id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560"
|
||||
}
|
||||
17
backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json
generated
Normal file
17
backend/.sqlx/query-cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cabc79429825b5f4861e4ba2001a5f235f39558dee88cbb486d7ac988d0a44d1"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cccf9e216d84dfbb801b92d697496f44d93b344190a26c8393867b3a268b9aba"
|
||||
}
|
||||
15
backend/.sqlx/query-d1d9cb0a7043760564171f481dd73f04eaa2c7d5c1ddc0501bc70c3fb34a07be.json
generated
Normal file
15
backend/.sqlx/query-d1d9cb0a7043760564171f481dd73f04eaa2c7d5c1ddc0501bc70c3fb34a07be.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM ai_agent_memory\n WHERE workspace_id = $1 AND conversation_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d1d9cb0a7043760564171f481dd73f04eaa2c7d5c1ddc0501bc70c3fb34a07be"
|
||||
}
|
||||
20
backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json
generated
Normal file
20
backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM unique_ext_jwt_token WHERE last_used_at > NOW() - INTERVAL '30 days'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n ",
|
||||
"query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND enabled IS TRUE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -68,5 +68,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "bbd51f4f0a8bb2db5d6b634f2d32f5f7f7f57390a0e4b6e7cad45c9147fd6356"
|
||||
"hash": "df6972fb16a2364f10379fe37e125ae228697602e3769c479582470dc0b9488b"
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT tag, dedicated_worker, flow_version.value->>'early_return' as early_return, flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor, (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled, on_behalf_of_email, edited_by, flow_version.id AS version\n FROM flow\n INNER JOIN flow_version\n ON flow_version.id = $3\n WHERE flow.path = $1 and flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tag",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "dedicated_worker",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "early_return",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "has_preprocessor",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "chat_input_enabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "version",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e1409c67b93881cf68f6ac9c8bae0856cf426c7e7860c8b5b799972baa8e6945"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) \n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group",
|
||||
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory, job_isolation) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (worker)\n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,10 +13,11 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Int8"
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2"
|
||||
"hash": "ed90b9fcc57f530bab2d2d7426795bb358c91ebdd9dab50d7e3d2fdce63b947c"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1",
|
||||
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE\n id = $1\n AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -25,5 +25,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e2474b7855c8b08f927f2b987421e773e537a8eb0a113477764bbab14e1f3a3d"
|
||||
"hash": "f0c9670c27d9a3b8e8f316fd28f7d3ede2ecc3225ff2879ff5dfd59c4ef236d5"
|
||||
}
|
||||
24
backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json
generated
Normal file
24
backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833"
|
||||
"hash": "f844026597d2e1c59a80dc8e1ddd069c0ad82b27b41171825928a938cfa8316f"
|
||||
}
|
||||
23
backend/.sqlx/query-fc9753f501974c4b570d710c7621ff1f6787a852bb27acf49593af963a6aacca.json
generated
Normal file
23
backend/.sqlx/query-fc9753f501974c4b570d710c7621ff1f6787a852bb27acf49593af963a6aacca.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM flow_version WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fc9753f501974c4b570d710c7621ff1f6787a852bb27acf49593af963a6aacca"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10\n WHERE \n workspace_id = $11 AND path = $12\n ",
|
||||
"query": "\n UPDATE email_trigger \n SET \n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n enabled = $11\n WHERE \n workspace_id = $12 AND path = $13\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -15,11 +15,12 @@
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6fafc23924eded970689040bd4a94d4d23ebee4f2b7d37bb54c47edb7720be00"
|
||||
"hash": "fe12006498b9e7aece6b104fc5d9a80eeac12c6ca07a0e8950941ba796b0ba79"
|
||||
}
|
||||
@@ -10,3 +10,16 @@
|
||||
|
||||
1. Update database schema with migration if necessary
|
||||
2. Update backend/windmill-api/openapi.yaml after modifying API endpoints
|
||||
|
||||
## Querying the Database
|
||||
|
||||
To query the database directly, use psql with the following connection string:
|
||||
|
||||
```bash
|
||||
psql postgres://postgres:changeme@localhost:5432/windmill
|
||||
```
|
||||
|
||||
This can be helpful for:
|
||||
- Inspecting database state during development
|
||||
- Testing queries before implementing them in Rust
|
||||
- Debugging data-related issues
|
||||
|
||||
572
backend/Cargo.lock
generated
572
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.573.1"
|
||||
version = "1.580.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.573.1"
|
||||
version = "1.580.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -161,6 +161,10 @@ nom.workspace = true
|
||||
globset.workspace = true
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-service = "0.7"
|
||||
windows-sys = { version = "0.52", features = ["Win32_System_Services", "Win32_System_Console", "Win32_Foundation"] }
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { optional = true, workspace = true }
|
||||
tikv-jemalloc-sys = { optional = true, workspace = true }
|
||||
@@ -252,17 +256,19 @@ urlencoding = "^2"
|
||||
url = { version = "^2" , features = ["serde"]}
|
||||
async-oauth2 = "0.5.1"
|
||||
reqwest = { version = "^0.12", features = ["json", "stream", "gzip", "multipart"] }
|
||||
eventsource-stream = "0.2.3"
|
||||
time = "^0"
|
||||
serde_urlencoded = "^0"
|
||||
tokio-tar = "^0"
|
||||
tempfile = "^3"
|
||||
tokio-util = { version = "^0", features = ["io"] }
|
||||
json-pointer = "^0"
|
||||
itertools = "^0"
|
||||
itertools = "^0.14.0"
|
||||
regex = "^1"
|
||||
semver = "^1"
|
||||
aws-sigv4 = "^1.3.4"
|
||||
aws-sdk-config = "=1.68.0"
|
||||
aws-sdk-rds = "^1"
|
||||
async-trait = "0.1.88"
|
||||
|
||||
|
||||
@@ -299,7 +305,7 @@ swc_ecma_visit = "=0.104.8"
|
||||
|
||||
|
||||
async-recursion = "^1"
|
||||
base64 = "^0"
|
||||
base64 = "^0.22.1"
|
||||
base32 = "^0"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
@@ -327,6 +333,7 @@ dyn-iter = "0.2.0"
|
||||
rsa = "^0"
|
||||
async_zip = { version = "0.0.17", features = ["tokio", "tokio-fs", "deflate", "chrono"] }
|
||||
once_cell = "1.17.1"
|
||||
dashmap = "6.1.0"
|
||||
gosyn = "0.2.6"
|
||||
bytes = "1.4.0"
|
||||
gethostname = "0.4.3"
|
||||
@@ -379,11 +386,14 @@ datafusion = "47.0.0"
|
||||
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7", features = ["aws", "azure", "gcp"] }
|
||||
openidconnect = { version = "4.0.0-rc.1" }
|
||||
aws-config = "^1"
|
||||
aws-sdk-bedrockruntime = "=1.113.0"
|
||||
aws-credential-types = "^1"
|
||||
aws-smithy-types = "^1"
|
||||
aws-sdk-sqs = "=1.77.0"
|
||||
aws-sdk-sts = "=1.79.0"
|
||||
aws-sdk-sso = "=1.77.0"
|
||||
aws-sdk-ssooidc = "=1.78.0"
|
||||
rustls = "=0.23.29"
|
||||
rustls = "=0.23.35"
|
||||
async-once-cell = "0.5.4"
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
8ab9b8d3cb7486fcb472d34f3a0b49f52548fe8f
|
||||
5dd7ab7ce9e78e1b96ea78fd297da95d9b69e7a3
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE http_trigger DROP COLUMN enabled;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE http_trigger ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add down migration script here
|
||||
|
||||
-- Drop table
|
||||
DROP TABLE IF EXISTS ai_agent_memory;
|
||||
16
backend/migrations/20251104135208_ai_agent_memory.up.sql
Normal file
16
backend/migrations/20251104135208_ai_agent_memory.up.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Add up migration script here
|
||||
|
||||
-- Create ai_agent_memory table for storing AI agent step memory when S3 is unavailable
|
||||
CREATE TABLE ai_agent_memory (
|
||||
workspace_id VARCHAR(50) NOT NULL,
|
||||
conversation_id UUID NOT NULL,
|
||||
step_id VARCHAR(255) NOT NULL,
|
||||
messages JSONB NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (workspace_id, conversation_id, step_id)
|
||||
);
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL ON ai_agent_memory TO windmill_admin;
|
||||
GRANT ALL ON ai_agent_memory TO windmill_user;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,99 @@
|
||||
CREATE OR REPLACE FUNCTION update_string(s text)
|
||||
RETURNS text
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
prefix TEXT := '-- https://www.windmill.dev/docs/getting_started/scripts_quickstart/sql#result-collection
|
||||
-- result_collection=legacy
|
||||
|
||||
';
|
||||
BEGIN
|
||||
RETURN prefix || s;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_all_modules(obj jsonb)
|
||||
RETURNS jsonb
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
result jsonb;
|
||||
k text;
|
||||
v jsonb;
|
||||
BEGIN
|
||||
IF jsonb_typeof(obj) = 'object' THEN
|
||||
result := '{}'::jsonb;
|
||||
|
||||
FOR k, v IN SELECT * FROM jsonb_each(obj)
|
||||
LOOP
|
||||
IF k = 'content' and jsonb_typeof(v) = 'string' AND obj->>'language' IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql') THEN
|
||||
result := result || jsonb_build_object('content', update_string(obj->>'content'));
|
||||
ELSE
|
||||
result := result || jsonb_build_object(k, update_all_modules(v));
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN result;
|
||||
ELSIF jsonb_typeof(obj) = 'array' AND jsonb_array_length(obj) > 0 THEN
|
||||
SELECT jsonb_agg(update_all_modules(elem))
|
||||
INTO result
|
||||
FROM jsonb_array_elements(obj) elem;
|
||||
|
||||
RETURN result;
|
||||
ELSE
|
||||
RETURN obj;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Run on a flow_version_lite jsonb value. Returns an array of flow_node ids whose languages are SQL.
|
||||
CREATE OR REPLACE FUNCTION find_sql_flow_nodes_ids(obj jsonb)
|
||||
RETURNS BIGINT[]
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
result BIGINT[] := '{}';
|
||||
k text;
|
||||
v jsonb;
|
||||
BEGIN
|
||||
IF jsonb_typeof(obj) = 'object' THEN
|
||||
IF obj->>'language' IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql') AND jsonb_typeof(obj->'id') = 'number' THEN
|
||||
result := result || (obj->>'id')::BIGINT;
|
||||
END IF;
|
||||
|
||||
FOR k, v IN SELECT * FROM jsonb_each(obj)
|
||||
LOOP
|
||||
result := result || find_sql_flow_nodes_ids(v);
|
||||
END LOOP;
|
||||
ELSIF jsonb_typeof(obj) = 'array' AND jsonb_array_length(obj) > 0 THEN
|
||||
SELECT array_agg(result_ids)
|
||||
INTO result
|
||||
FROM jsonb_array_elements(obj) elem, unnest(find_sql_flow_nodes_ids(elem)) as result_ids;
|
||||
END IF;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
UPDATE app_version SET value = update_all_modules(value::jsonb)::json;
|
||||
UPDATE draft SET value = update_all_modules(value::jsonb)::json;
|
||||
UPDATE flow SET value = update_all_modules(value);
|
||||
UPDATE flow_version SET value = update_all_modules(value);
|
||||
UPDATE flow_node SET code = update_string(code) WHERE id IN (
|
||||
SELECT v FROM flow_version_lite, unnest(find_sql_flow_nodes_ids(value)) as v
|
||||
);
|
||||
UPDATE app_script SET code = update_string(code) WHERE id IN (
|
||||
SELECT v FROM app_version_lite, unnest(find_sql_flow_nodes_ids(value)) as v
|
||||
);
|
||||
UPDATE script SET content = update_string(content) WHERE language IN ('bigquery', 'postgresql', 'duckdb', 'mssql', 'oracledb', 'snowflake', 'mysql');
|
||||
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- ✅ LOG ERROR WITHOUT STOPPING THE MIGRATION
|
||||
RAISE WARNING 'Migration failed: %', SQLERRM;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP FUNCTION IF EXISTS update_all_modules(jsonb);
|
||||
DROP FUNCTION IF EXISTS update_string(text);
|
||||
DROP FUNCTION IF EXISTS find_sql_flow_nodes_ids(jsonb);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Rollback: Remove job_isolation column from worker_ping table
|
||||
ALTER TABLE worker_ping DROP COLUMN IF EXISTS job_isolation;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add job_isolation column to worker_ping table
|
||||
-- This tracks which job isolation method the worker is using: 'nsjail', 'unshare', or 'none'
|
||||
-- Nullable for backwards compatibility - old workers will report NULL
|
||||
ALTER TABLE worker_ping ADD COLUMN job_isolation TEXT;
|
||||
1
backend/migrations/20251117222148_email_enabled.down.sql
Normal file
1
backend/migrations/20251117222148_email_enabled.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
3
backend/migrations/20251117222148_email_enabled.up.sql
Normal file
3
backend/migrations/20251117222148_email_enabled.up.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
|
||||
ALTER TABLE email_trigger ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
|
||||
DROP TABLE IF EXISTS unique_ext_jwt_token;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Add up migration script here
|
||||
|
||||
CREATE TABLE IF NOT EXISTS unique_ext_jwt_token (
|
||||
jwt_hash BIGINT PRIMARY KEY NOT NULL,
|
||||
last_used_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_unique_ext_jwt_token_last_used_at ON unique_ext_jwt_token(last_used_at);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,23 +44,41 @@ fn parse_default_expr(e: Expression) -> Option<Value> {
|
||||
|
||||
pub fn parse_php_signature(
|
||||
code: &str,
|
||||
override_main: Option<String>,
|
||||
override_entrypoint: Option<String>,
|
||||
) -> anyhow::Result<MainArgSignature> {
|
||||
let main_name = override_main.unwrap_or("main".to_string());
|
||||
let entrypoint_fn_name = override_entrypoint.unwrap_or("main".to_string());
|
||||
|
||||
let ast = parser::parse(code)
|
||||
.map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?;
|
||||
|
||||
let params = ast.into_iter().find_map(|x| match x {
|
||||
Statement::Function(FunctionStatement {
|
||||
name,
|
||||
parameters: FunctionParameterList { parameters, .. },
|
||||
..
|
||||
}) if name.to_string() == main_name => Some(parameters),
|
||||
_ => None,
|
||||
});
|
||||
let mut entrypoint_params = None;
|
||||
let mut has_preprocessor = None;
|
||||
for node in ast.into_iter() {
|
||||
match node {
|
||||
Statement::Function(FunctionStatement {
|
||||
name,
|
||||
parameters: FunctionParameterList { parameters, .. },
|
||||
..
|
||||
}) => {
|
||||
let fn_name = name.to_string();
|
||||
|
||||
if let Some(params) = params {
|
||||
if has_preprocessor.is_none() && fn_name == "preprocessor" {
|
||||
has_preprocessor = Some(true);
|
||||
}
|
||||
|
||||
if entrypoint_params.is_none() && fn_name == entrypoint_fn_name {
|
||||
entrypoint_params = Some(parameters);
|
||||
}
|
||||
|
||||
if has_preprocessor.is_some() && entrypoint_params.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(params) = entrypoint_params {
|
||||
let args = params
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
@@ -82,7 +100,7 @@ pub fn parse_php_signature(
|
||||
star_kwargs: false,
|
||||
args,
|
||||
no_main_func: Some(false),
|
||||
has_preprocessor: None,
|
||||
has_preprocessor,
|
||||
})
|
||||
} else {
|
||||
Ok(MainArgSignature {
|
||||
@@ -90,7 +108,7 @@ pub fn parse_php_signature(
|
||||
star_kwargs: false,
|
||||
args: vec![],
|
||||
no_main_func: Some(true),
|
||||
has_preprocessor: None,
|
||||
has_preprocessor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +134,11 @@ pub fn parse_graphql(code: &str) -> String {
|
||||
|
||||
#[cfg(feature = "php-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_php(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_php::parse_php_signature(code, None))
|
||||
pub fn parse_php(code: &str, main_override: Option<String>) -> String {
|
||||
wrap_sig(windmill_parser_php::parse_php_signature(
|
||||
code,
|
||||
main_override,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-parser")]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Run the command repeatedly until it fails (exits with non-zero code)
|
||||
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
|
||||
while true; do
|
||||
DISABLE_EMBEDDING=true \
|
||||
RUST_LOG=info \
|
||||
@@ -9,7 +10,7 @@ while true; do
|
||||
GO_PATH=$(which go) \
|
||||
UV_PATH=$(which uv) \
|
||||
CARGO_PATH=$(which cargo) \
|
||||
cargo test --features enterprise,deno_core,license,python,rust,scoped_cache \
|
||||
cargo test --features enterprise,deno_core,license,python,duckdb,rust,scoped_cache \
|
||||
-- --nocapture --test-threads=8 | tee /tmp/test.log
|
||||
|
||||
# Capture the exit code of the cargo test command (not tee)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn set_license_key(_license_key: String) -> () {
|
||||
pub async fn set_license_key(_license_key: String, _db: Option<&windmill_common::db::DB>) -> () {
|
||||
// Implementation is not open source
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,10 @@ pub mod ee;
|
||||
mod ee_oss;
|
||||
mod monitor;
|
||||
|
||||
// Windows service support - EE feature
|
||||
#[cfg(all(windows, feature = "enterprise", feature = "private"))]
|
||||
mod windows_service_ee;
|
||||
|
||||
pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
// https://github.com/denoland/deno/blob/main/cli/main.rs#L477
|
||||
#[cfg(feature = "deno_core")]
|
||||
@@ -207,6 +211,17 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
// On Windows with enterprise feature, check if running as a service
|
||||
#[cfg(all(windows, feature = "enterprise", feature = "private"))]
|
||||
{
|
||||
if windows_service_ee::is_running_as_service() {
|
||||
// Run as Windows service with SCM handlers
|
||||
return windows_service_ee::run_as_windows_service()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to run as Windows service: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// Normal execution (console/foreground mode)
|
||||
setup_deno_runtime()?;
|
||||
create_and_run_current_thread_inner(windmill_main())
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ use windmill_common::{
|
||||
OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS,
|
||||
};
|
||||
use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING};
|
||||
use windmill_queue::{SameWorkerPayload, cancel_job, get_queued_job_v2};
|
||||
use windmill_queue::{cancel_job, get_queued_job_v2, SameWorkerPayload};
|
||||
use windmill_worker::{
|
||||
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
|
||||
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
|
||||
@@ -1271,7 +1271,7 @@ pub async fn reload_license_key(conn: &Connection) -> anyhow::Result<()> {
|
||||
tracing::error!("Could not parse LICENSE_KEY found: {:#?}", &q);
|
||||
}
|
||||
};
|
||||
set_license_key(value).await;
|
||||
set_license_key(value, conn.as_sql()).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2219,7 +2219,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
timeouts
|
||||
};
|
||||
|
||||
@@ -2253,7 +2252,6 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let timeouts = non_restartable_jobs
|
||||
.into_iter()
|
||||
.map(|x| (x, ErrorMessage::RestartDisabled))
|
||||
@@ -2289,59 +2287,59 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
|
||||
continue;
|
||||
}
|
||||
if let Some(job) = job.unwrap() {
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
{
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&job.permissioned_as_email,
|
||||
&job.id,
|
||||
None,
|
||||
Some(format!("handle_zombie_jobs")),
|
||||
)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
{
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&job.permissioned_as_email,
|
||||
&job.id,
|
||||
None,
|
||||
Some(format!("handle_zombie_jobs")),
|
||||
)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
|
||||
let client = AuthedClient::new(
|
||||
base_internal_url.to_string(),
|
||||
job.workspace_id.to_string(),
|
||||
token,
|
||||
None,
|
||||
);
|
||||
let client = AuthedClient::new(
|
||||
base_internal_url.to_string(),
|
||||
job.workspace_id.to_string(),
|
||||
token,
|
||||
None,
|
||||
);
|
||||
|
||||
let error_message = format!(
|
||||
let error_message = format!(
|
||||
"Job timed out after no ping from job since {} (ZOMBIE_JOB_TIMEOUT: {}, reason: {:?}).\nThis likely means that the job died on worker {}, OOM are a common reason for worker crashes.\nCheck the workers around the time of the last ping and the exit code if any.",
|
||||
job.last_ping.unwrap_or_default(),
|
||||
*ZOMBIE_JOB_TIMEOUT,
|
||||
error_kind.to_string(),
|
||||
job.worker.clone().unwrap_or_default(),
|
||||
);
|
||||
let memory_peak = job.memory_peak.unwrap_or(0);
|
||||
let _ = handle_job_error(
|
||||
db,
|
||||
&client,
|
||||
&windmill_queue::MiniCompletedJob::from(job),
|
||||
memory_peak,
|
||||
None,
|
||||
error::Error::ExecutionErr(error_message),
|
||||
true,
|
||||
Some(&same_worker_tx_never_used),
|
||||
"",
|
||||
node_name,
|
||||
send_result_never_used,
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut windmill_common::bench::BenchmarkIter::new(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let memory_peak = job.memory_peak.unwrap_or(0);
|
||||
let _ = handle_job_error(
|
||||
db,
|
||||
&client,
|
||||
&windmill_queue::MiniCompletedJob::from(job),
|
||||
memory_peak,
|
||||
None,
|
||||
error::Error::ExecutionErr(error_message),
|
||||
true,
|
||||
Some(&same_worker_tx_never_used),
|
||||
"",
|
||||
node_name,
|
||||
send_result_never_used,
|
||||
#[cfg(feature = "benchmark")]
|
||||
&mut windmill_common::bench::BenchmarkIter::new(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
|
||||
|
||||
@@ -208,6 +208,7 @@ impl RunJob {
|
||||
false,
|
||||
None,
|
||||
debounce_job_id_o,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("push has to succeed");
|
||||
|
||||
@@ -421,14 +421,16 @@ def main():
|
||||
path: "f/rel/root_flow_renamed".into(),
|
||||
summary: "".into(),
|
||||
description: None,
|
||||
value: to_raw_value(&serde_json::from_str::<serde_json::Value>(
|
||||
&serde_json::to_string(flow.value())
|
||||
.unwrap()
|
||||
.replace("nstep1", "Foxes")
|
||||
.replace("nstep2_2", "like")
|
||||
.replace("nstep_4_1", "Emeralds"),
|
||||
)
|
||||
.unwrap()),
|
||||
value: to_raw_value(
|
||||
&serde_json::from_str::<serde_json::Value>(
|
||||
&serde_json::to_string(flow.value())
|
||||
.unwrap()
|
||||
.replace("nstep1", "Foxes")
|
||||
.replace("nstep2_2", "like")
|
||||
.replace("nstep_4_1", "Emeralds"),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
schema: None,
|
||||
draft_only: None,
|
||||
tag: None,
|
||||
@@ -437,7 +439,7 @@ def main():
|
||||
deployment_message: None,
|
||||
visible_to_runner_only: None,
|
||||
on_behalf_of_email: None,
|
||||
ws_error_handler_muted: None
|
||||
ws_error_handler_muted: None,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
@@ -862,6 +864,7 @@ def main():
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1021,6 +1024,7 @@ def main():
|
||||
false,
|
||||
None,
|
||||
debounce_job_id_o,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1198,6 +1202,7 @@ def main():
|
||||
false,
|
||||
None,
|
||||
debounce_job_id_o,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1705,6 +1710,7 @@ WHERE
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -1844,6 +1850,7 @@ WHERE
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -2274,6 +2281,7 @@ WHERE
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -2399,6 +2407,7 @@ WHERE
|
||||
// false,
|
||||
// None,
|
||||
// None,
|
||||
// None,
|
||||
// )
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
@@ -2769,7 +2769,7 @@ async fn test_result_format(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let response = windmill_api::jobs::run_wait_result(
|
||||
&db,
|
||||
Uuid::parse_str(ordered_result_job_id).unwrap(),
|
||||
"test-workspace".to_string(),
|
||||
"test-workspace",
|
||||
None,
|
||||
"test-user",
|
||||
)
|
||||
@@ -2944,3 +2944,33 @@ async fn test_workflow_as_code(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "duckdb")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_duckdb_ffi(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
let content = "-- result_collection=last_statement_first_row_scalar\nSELECT 'Hello world!';";
|
||||
|
||||
let flow: FlowValue = serde_json::from_value(serde_json::json!({
|
||||
"modules": [{
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "duckdb",
|
||||
"content": content,
|
||||
},
|
||||
}],
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None, restarted_from: None })
|
||||
.run_until_complete(&db, false, server.addr.port())
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
assert_eq!(result, serde_json::json!("Hello world!"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ tempfile.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio-tar.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
postgres-native-tls.workspace = true
|
||||
hmac.workspace = true
|
||||
cookie.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.573.1
|
||||
version: 1.580.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -572,12 +572,12 @@ paths:
|
||||
use_case:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Onboarding data submitted successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
type: string
|
||||
|
||||
/w/{workspace}/users/delete/{username}:
|
||||
delete:
|
||||
@@ -728,6 +728,32 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/create_workspace_fork_branch:
|
||||
post:
|
||||
summary: create forked workspace branch with git sync
|
||||
operationId: createWorkspaceForkGitBranch
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: new forked workspace
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateWorkspaceFork"
|
||||
responses:
|
||||
"201":
|
||||
description: forked workspace branch created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
/w/{workspace}/workspaces/create_fork:
|
||||
post:
|
||||
summary: create forked workspace
|
||||
@@ -5930,6 +5956,80 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_wait_result/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version and wait until completion
|
||||
operationId: runWaitResultFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
get:
|
||||
summary: run flow by version with GET and wait until completion
|
||||
operationId: runWaitResultFlowByVersionGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and stream updates via SSE
|
||||
@@ -6006,6 +6106,94 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version and stream updates via SSE
|
||||
operationId: runAndStreamFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
get:
|
||||
summary: run flow by version with GET and stream updates via SSE
|
||||
operationId: runAndStreamFlowByVersionGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: poll_delay_ms
|
||||
description: delay between polling for job updates in milliseconds
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: server-sent events stream of job updates
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/jobs/run_and_stream/p/{path}:
|
||||
post:
|
||||
summary: run script by path and stream updates via SSE
|
||||
@@ -7654,6 +7842,64 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
/w/{workspace}/jobs/run/fv/{version}:
|
||||
post:
|
||||
summary: run flow by version
|
||||
operationId: runFlowByVersion
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: version
|
||||
description: flow version ID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- name: scheduled_for
|
||||
description: when to schedule this job (leave empty for immediate run)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/SkipPreprocessor"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: flow args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
responses:
|
||||
"201":
|
||||
description: job created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
/w/{workspace}/jobs/run/batch_rerun_jobs:
|
||||
post:
|
||||
summary: re-run multiple jobs
|
||||
@@ -7968,6 +8214,12 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: preview
|
||||
@@ -7994,6 +8246,12 @@ paths:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: memory_id
|
||||
description: memory ID for chat-enabled flows
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
requestBody:
|
||||
description: preview
|
||||
@@ -8413,6 +8671,7 @@ paths:
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
- $ref: "#/components/parameters/AllowWildcards"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- $ref: "#/components/parameters/JobTriggerKind"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -9904,6 +10163,34 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/http_triggers/setenabled/{path}:
|
||||
post:
|
||||
summary: enable/disable http trigger
|
||||
operationId: setHttpTriggerEnabled
|
||||
tags:
|
||||
- http_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- enabled
|
||||
responses:
|
||||
"200":
|
||||
description: http trigger enable/disable
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/websocket_triggers/create:
|
||||
post:
|
||||
summary: create websocket trigger
|
||||
@@ -11711,6 +11998,33 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
/w/{workspace}/email_triggers/setenabled/{path}:
|
||||
post:
|
||||
summary: enable/disable email trigger
|
||||
operationId: setEmailTriggerEnabled
|
||||
tags:
|
||||
- email_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- enabled
|
||||
responses:
|
||||
"200":
|
||||
description: email trigger enable/disable
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/groups/list:
|
||||
get:
|
||||
@@ -14386,6 +14700,7 @@ paths:
|
||||
- $ref: "#/components/parameters/AllowWildcards"
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- $ref: "#/components/parameters/JobTriggerKind"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -14824,6 +15139,27 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
JobTriggerKind:
|
||||
name: trigger_kind
|
||||
description: trigger kind (schedule, http, websocket...)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- webhook
|
||||
- default_email
|
||||
- email
|
||||
- schedule
|
||||
- http
|
||||
- websocket
|
||||
- postgres
|
||||
- kafka
|
||||
- nats
|
||||
- mqtt
|
||||
- sqs
|
||||
- gcp
|
||||
- poll
|
||||
- cli
|
||||
OrderDesc:
|
||||
name: order_desc
|
||||
description: order by desc order (default true)
|
||||
@@ -14985,8 +15321,7 @@ components:
|
||||
|
||||
CreatedAfterQueue:
|
||||
name: created_after_queue
|
||||
description:
|
||||
filter on jobs created after X for jobs in the queue only
|
||||
description: filter on jobs created after X for jobs in the queue only
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -14994,8 +15329,7 @@ components:
|
||||
|
||||
CreatedBeforeQueue:
|
||||
name: created_before_queue
|
||||
description:
|
||||
filter on jobs created before X for jobs in the queue only
|
||||
description: filter on jobs created before X for jobs in the queue only
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -15290,6 +15624,7 @@ components:
|
||||
groq,
|
||||
openrouter,
|
||||
togetherai,
|
||||
aws_bedrock,
|
||||
customai,
|
||||
]
|
||||
|
||||
@@ -16441,30 +16776,30 @@ components:
|
||||
ScriptLang:
|
||||
type: string
|
||||
enum: [
|
||||
python3,
|
||||
deno,
|
||||
go,
|
||||
bash,
|
||||
powershell,
|
||||
postgresql,
|
||||
mysql,
|
||||
bigquery,
|
||||
snowflake,
|
||||
mssql,
|
||||
oracledb,
|
||||
graphql,
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
nu,
|
||||
java,
|
||||
ruby,
|
||||
duckdb,
|
||||
# for related places search: ADD_NEW_LANG
|
||||
]
|
||||
python3,
|
||||
deno,
|
||||
go,
|
||||
bash,
|
||||
powershell,
|
||||
postgresql,
|
||||
mysql,
|
||||
bigquery,
|
||||
snowflake,
|
||||
mssql,
|
||||
oracledb,
|
||||
graphql,
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
ansible,
|
||||
csharp,
|
||||
nu,
|
||||
java,
|
||||
ruby,
|
||||
duckdb,
|
||||
# for related places search: ADD_NEW_LANG
|
||||
]
|
||||
|
||||
Preview:
|
||||
type: object
|
||||
@@ -16941,6 +17276,8 @@ components:
|
||||
format: date-time
|
||||
is_flow:
|
||||
type: boolean
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -16950,6 +17287,7 @@ components:
|
||||
- edited_by
|
||||
- edited_at
|
||||
- is_flow
|
||||
- enabled
|
||||
|
||||
AuthenticationMethod:
|
||||
type: string
|
||||
@@ -17175,6 +17513,8 @@ components:
|
||||
type: boolean
|
||||
wrap_body:
|
||||
type: boolean
|
||||
enabled:
|
||||
type: boolean
|
||||
raw_string:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
@@ -17300,8 +17640,6 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
filters:
|
||||
type: array
|
||||
items:
|
||||
@@ -17332,7 +17670,6 @@ components:
|
||||
|
||||
required:
|
||||
- url
|
||||
- enabled
|
||||
- filters
|
||||
- can_return_message
|
||||
- can_return_error_result
|
||||
@@ -17521,17 +17858,13 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
|
||||
required:
|
||||
- enabled
|
||||
- subscribe_topics
|
||||
- mqtt_resource_path
|
||||
|
||||
@@ -17653,8 +17986,6 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
@@ -17665,7 +17996,6 @@ components:
|
||||
- gcp_resource_path
|
||||
- topic_id
|
||||
- subscription_id
|
||||
- enabled
|
||||
- delivery_type
|
||||
- subscription_mode
|
||||
|
||||
@@ -17767,8 +18097,6 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
@@ -17779,7 +18107,6 @@ components:
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
- enabled
|
||||
- aws_auth_resource_type
|
||||
|
||||
LoggedWizardStatus:
|
||||
@@ -17976,8 +18303,6 @@ components:
|
||||
- $ref: "#/components/schemas/TriggerExtraProperty"
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
postgres_resource_path:
|
||||
type: string
|
||||
publication_name:
|
||||
@@ -17998,7 +18323,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
required:
|
||||
- enabled
|
||||
- postgres_resource_path
|
||||
- replication_slot_name
|
||||
- publication_name
|
||||
@@ -18089,8 +18413,6 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
@@ -18102,7 +18424,6 @@ components:
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
- topics
|
||||
- enabled
|
||||
|
||||
NewKafkaTrigger:
|
||||
type: object
|
||||
@@ -18194,8 +18515,6 @@ components:
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
@@ -18207,7 +18526,6 @@ components:
|
||||
- nats_resource_path
|
||||
- use_jetstream
|
||||
- subjects
|
||||
- enabled
|
||||
|
||||
NewNatsTrigger:
|
||||
type: object
|
||||
@@ -18321,7 +18639,8 @@ components:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -18483,6 +18802,8 @@ components:
|
||||
type: number
|
||||
wm_memory_usage:
|
||||
type: number
|
||||
job_isolation:
|
||||
type: string
|
||||
required:
|
||||
- worker
|
||||
- worker_instance
|
||||
@@ -18660,6 +18981,8 @@ components:
|
||||
properties:
|
||||
lock_error_logs:
|
||||
type: string
|
||||
version_id:
|
||||
type: number
|
||||
|
||||
ExtraPerms:
|
||||
type: object
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::bedrock;
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
|
||||
use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router};
|
||||
@@ -6,12 +7,12 @@ use quick_cache::sync::Cache;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use windmill_common::variables::get_variable_or_self;
|
||||
use std::collections::HashMap;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel, AZURE_API_VERSION};
|
||||
use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel};
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
use windmill_common::utils::configure_client;
|
||||
use windmill_common::variables::get_variable_or_self;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HTTP_CLIENT: Client = configure_client(reqwest::ClientBuilder::new()
|
||||
@@ -66,6 +67,7 @@ struct AIStandardResource {
|
||||
#[serde(alias = "apiKey")]
|
||||
api_key: Option<String>,
|
||||
organization_id: Option<String>,
|
||||
region: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -98,7 +100,9 @@ impl AIRequestConfig {
|
||||
) -> Result<Self> {
|
||||
let (api_key, access_token, organization_id, base_url, user) = match resource {
|
||||
AIResource::Standard(resource) => {
|
||||
let base_url = provider.get_base_url(resource.base_url, db).await?;
|
||||
let base_url = provider
|
||||
.get_base_url(resource.base_url, resource.region, db)
|
||||
.await?;
|
||||
let api_key = if let Some(api_key) = resource.api_key {
|
||||
Some(get_variable_or_self(api_key, db, w_id).await?)
|
||||
} else {
|
||||
@@ -119,7 +123,7 @@ impl AIRequestConfig {
|
||||
None
|
||||
};
|
||||
let token = Self::get_token_using_oauth(resource, db, w_id).await?;
|
||||
let base_url = provider.get_base_url(None, db).await?;
|
||||
let base_url = provider.get_base_url(None, None, db).await?;
|
||||
|
||||
(None, Some(token), None, base_url, user)
|
||||
}
|
||||
@@ -180,15 +184,34 @@ impl AIRequestConfig {
|
||||
let is_azure = provider.is_azure_openai(base_url);
|
||||
let is_anthropic = matches!(provider, AIProvider::Anthropic);
|
||||
let is_anthropic_sdk = headers.get("X-Anthropic-SDK").is_some();
|
||||
let is_bedrock = matches!(provider, AIProvider::AWSBedrock);
|
||||
|
||||
let url = if is_azure && method != Method::GET {
|
||||
let model = AIProvider::extract_model_from_body(&body)?;
|
||||
AIProvider::build_azure_openai_url(base_url, &model, path)
|
||||
// Handle AWS Bedrock transformation
|
||||
let (url, body) = if is_bedrock && method != Method::GET {
|
||||
let (model, transformed_body, is_streaming) =
|
||||
bedrock::transform_openai_to_bedrock(&body)?;
|
||||
let endpoint = if is_streaming {
|
||||
"converse-stream"
|
||||
} else {
|
||||
"converse"
|
||||
};
|
||||
let bedrock_url = format!("{}/model/{}/{}", base_url, model, endpoint);
|
||||
(bedrock_url, transformed_body)
|
||||
} else if is_bedrock && (path == "foundation-models" || path == "inference-profiles") {
|
||||
// AWS Bedrock foundation-models and inference-profiles endpoints use different base URL (without -runtime)
|
||||
let bedrock_base_url = base_url.replace("bedrock-runtime.", "bedrock.");
|
||||
let bedrock_url = format!("{}/{}", bedrock_base_url, path);
|
||||
(bedrock_url, body)
|
||||
} else if is_azure {
|
||||
let azure_url = AIProvider::build_azure_openai_url(base_url, path);
|
||||
(azure_url, body)
|
||||
} else if is_anthropic_sdk {
|
||||
let truncated_base_url = base_url.trim_end_matches("/v1");
|
||||
format!("{}/{}", truncated_base_url, path)
|
||||
let anthropic_url = format!("{}/{}", truncated_base_url, path);
|
||||
(anthropic_url, body)
|
||||
} else {
|
||||
format!("{}/{}", base_url, path)
|
||||
let default_url = format!("{}/{}", base_url, path);
|
||||
(default_url, body)
|
||||
};
|
||||
|
||||
tracing::debug!("AI request URL: {}", url);
|
||||
@@ -205,10 +228,6 @@ impl AIRequestConfig {
|
||||
|
||||
request = request.body(body);
|
||||
|
||||
if is_azure {
|
||||
request = request.query(&[("api-version", AZURE_API_VERSION)])
|
||||
}
|
||||
|
||||
if let Some(api_key) = self.api_key {
|
||||
if is_azure {
|
||||
request = request.header("api-key", api_key.clone())
|
||||
@@ -316,7 +335,7 @@ async fn global_proxy(
|
||||
return Err(Error::BadRequest("API key is required".to_string()));
|
||||
};
|
||||
|
||||
let base_url = provider.get_base_url(None, &db).await?;
|
||||
let base_url = provider.get_base_url(None, None, &db).await?;
|
||||
|
||||
let url = format!("{}/{}", base_url, ai_path);
|
||||
|
||||
@@ -446,6 +465,21 @@ async fn proxy(
|
||||
}
|
||||
};
|
||||
|
||||
// Extract model and streaming flag for Bedrock transformation (only for POST requests)
|
||||
let (model, is_streaming) =
|
||||
if matches!(provider, AIProvider::AWSBedrock) && method == Method::POST {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BedrockRequest {
|
||||
model: String,
|
||||
stream: bool,
|
||||
}
|
||||
let parsed: BedrockRequest = serde_json::from_slice(&body)
|
||||
.map_err(|e| Error::internal_err(format!("Failed to parse request body: {}", e)))?;
|
||||
(Some(parsed.model), parsed.stream)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let request = request_config.prepare_request(&provider, &ai_path, method, headers, body)?;
|
||||
|
||||
let response = request.send().await.map_err(to_anyhow)?;
|
||||
@@ -469,8 +503,45 @@ async fn proxy(
|
||||
return Err(Error::AIError(err_msg));
|
||||
}
|
||||
|
||||
let status_code = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let stream = response.bytes_stream();
|
||||
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
|
||||
// Transform Bedrock responses back to OpenAI format
|
||||
if matches!(provider, AIProvider::AWSBedrock) && model.is_some() {
|
||||
if is_streaming {
|
||||
// Transform streaming response
|
||||
use http::StatusCode;
|
||||
|
||||
let mut response_headers = HeaderMap::new();
|
||||
response_headers.insert("content-type", "text/event-stream".parse().unwrap());
|
||||
response_headers.insert("cache-control", "no-cache".parse().unwrap());
|
||||
response_headers.insert("connection", "keep-alive".parse().unwrap());
|
||||
|
||||
let stream = response.bytes_stream();
|
||||
let transformed_stream =
|
||||
bedrock::transform_bedrock_stream_to_openai(stream, model.unwrap());
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
response_headers,
|
||||
axum::body::Body::from_stream(transformed_stream),
|
||||
))
|
||||
} else {
|
||||
// Transform non-streaming response
|
||||
let transformed_body =
|
||||
bedrock::transform_bedrock_to_openai(response, model.unwrap()).await?;
|
||||
|
||||
let mut response_headers = HeaderMap::new();
|
||||
response_headers.insert("content-type", "application/json".parse().unwrap());
|
||||
|
||||
Ok((
|
||||
http::StatusCode::OK,
|
||||
response_headers,
|
||||
axum::body::Body::from(transformed_body),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Pass through for other providers
|
||||
let status_code = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let stream = response.bytes_stream();
|
||||
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1241,6 +1241,7 @@ async fn create_app_internal<'a>(
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1630,6 +1631,7 @@ async fn update_app_internal<'a>(
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
@@ -1949,6 +1951,7 @@ async fn execute_component(
|
||||
false,
|
||||
end_user_email,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -90,6 +90,7 @@ impl AuthCache {
|
||||
w_id.as_ref(),
|
||||
token.trim_start_matches("jwt_ext_"),
|
||||
self.ext_jwks.clone(),
|
||||
&self.db,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
602
backend/windmill-api/src/bedrock.rs
Normal file
602
backend/windmill-api/src/bedrock.rs
Normal file
@@ -0,0 +1,602 @@
|
||||
use axum::body::Bytes;
|
||||
use bytes;
|
||||
use futures;
|
||||
use uuid;
|
||||
use windmill_common::error::{Error, Result};
|
||||
|
||||
/// Transform OpenAI format request to AWS Bedrock Converse format
|
||||
/// Returns: (model_id, transformed_body, is_streaming)
|
||||
pub fn transform_openai_to_bedrock(body: &[u8]) -> Result<(String, Bytes, bool)> {
|
||||
use serde_json::Value;
|
||||
|
||||
// Parse the OpenAI request
|
||||
let openai_req: Value = serde_json::from_slice(body)
|
||||
.map_err(|e| Error::internal_err(format!("Failed to parse OpenAI request: {}", e)))?;
|
||||
|
||||
// Extract model and streaming flag
|
||||
let model = openai_req["model"]
|
||||
.as_str()
|
||||
.ok_or_else(|| Error::BadRequest("Missing 'model' field in request".to_string()))?
|
||||
.to_string();
|
||||
|
||||
let is_streaming = openai_req["stream"].as_bool().unwrap_or(false);
|
||||
|
||||
// Build Bedrock request
|
||||
let mut bedrock_req = serde_json::json!({});
|
||||
|
||||
// Transform messages
|
||||
if let Some(messages) = openai_req["messages"].as_array() {
|
||||
let mut system_messages = Vec::new();
|
||||
let mut conversation_messages = Vec::new();
|
||||
|
||||
for msg in messages {
|
||||
let role = msg["role"].as_str().unwrap_or("");
|
||||
|
||||
match role {
|
||||
"system" => {
|
||||
// Extract system messages to separate array
|
||||
if let Some(content) = msg["content"].as_str() {
|
||||
system_messages.push(serde_json::json!({"text": content}));
|
||||
}
|
||||
}
|
||||
"user" | "assistant" => {
|
||||
// Normalize content to array format
|
||||
let mut content = if let Some(text) = msg["content"].as_str() {
|
||||
// Simple string → array of content blocks
|
||||
vec![serde_json::json!({"text": text})]
|
||||
} else if let Some(content_array) = msg["content"].as_array() {
|
||||
// Already an array - transform each item
|
||||
content_array
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if let Some(text) = item["text"].as_str() {
|
||||
Some(serde_json::json!({"text": text}))
|
||||
} else if item["type"].as_str() == Some("text") {
|
||||
Some(serde_json::json!({"text": item["text"]}))
|
||||
} else if item["type"].as_str() == Some("image_url") {
|
||||
// Transform image_url format if needed
|
||||
// For now, pass through - may need more sophisticated handling
|
||||
Some(item.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Handle tool_calls for assistant messages (OpenAI → Bedrock toolUse)
|
||||
if role == "assistant" {
|
||||
if let Some(tool_calls) = msg["tool_calls"].as_array() {
|
||||
for tool_call in tool_calls {
|
||||
if tool_call["type"].as_str() == Some("function") {
|
||||
let tool_use_id = tool_call["id"].as_str().unwrap_or("");
|
||||
let function_name =
|
||||
tool_call["function"]["name"].as_str().unwrap_or("");
|
||||
let arguments_str =
|
||||
tool_call["function"]["arguments"].as_str().unwrap_or("{}");
|
||||
|
||||
// Parse arguments JSON string to object
|
||||
let input = serde_json::from_str::<Value>(arguments_str)
|
||||
.map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Failed to parse tool call arguments: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
content.push(serde_json::json!({
|
||||
"toolUse": {
|
||||
"toolUseId": tool_use_id,
|
||||
"name": function_name,
|
||||
"input": input
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only add message if it has content
|
||||
if !content.is_empty() {
|
||||
conversation_messages.push(serde_json::json!({
|
||||
"role": role,
|
||||
"content": content
|
||||
}));
|
||||
}
|
||||
}
|
||||
"tool" => {
|
||||
// Transform tool response to Bedrock format
|
||||
let tool_call_id = msg["tool_call_id"].as_str().unwrap_or("");
|
||||
let content = msg["content"].as_str().unwrap_or("");
|
||||
|
||||
// Try to parse content as JSON
|
||||
// Bedrock requires json field to be an object, not a primitive or array
|
||||
let tool_result_content =
|
||||
if let Ok(json_content) = serde_json::from_str::<Value>(content) {
|
||||
if json_content.is_object() {
|
||||
vec![serde_json::json!({"json": json_content})]
|
||||
} else {
|
||||
// Wrap primitives and arrays in an object
|
||||
vec![serde_json::json!({"json": {"result": json_content}})]
|
||||
}
|
||||
} else {
|
||||
vec![serde_json::json!({"text": content})]
|
||||
};
|
||||
|
||||
conversation_messages.push(serde_json::json!({
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"toolResult": {
|
||||
"toolUseId": tool_call_id,
|
||||
"content": tool_result_content
|
||||
}
|
||||
}]
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !system_messages.is_empty() {
|
||||
bedrock_req["system"] = Value::Array(system_messages);
|
||||
}
|
||||
bedrock_req["messages"] = Value::Array(conversation_messages);
|
||||
}
|
||||
|
||||
// Transform inference parameters
|
||||
let mut inference_config = serde_json::json!({});
|
||||
if let Some(max_tokens) = openai_req["max_tokens"].as_i64() {
|
||||
inference_config["maxTokens"] = Value::Number(max_tokens.into());
|
||||
}
|
||||
if let Some(temperature) = openai_req["temperature"].as_f64() {
|
||||
inference_config["temperature"] = serde_json::json!(temperature);
|
||||
}
|
||||
if let Some(top_p) = openai_req["top_p"].as_f64() {
|
||||
inference_config["topP"] = serde_json::json!(top_p);
|
||||
}
|
||||
if let Some(stop) = openai_req["stop"].as_array() {
|
||||
let stop_sequences: Vec<String> = stop
|
||||
.iter()
|
||||
.filter_map(|s| s.as_str().map(|s| s.to_string()))
|
||||
.collect();
|
||||
if !stop_sequences.is_empty() {
|
||||
inference_config["stopSequences"] =
|
||||
Value::Array(stop_sequences.into_iter().map(Value::String).collect());
|
||||
}
|
||||
}
|
||||
if !inference_config.as_object().unwrap().is_empty() {
|
||||
bedrock_req["inferenceConfig"] = inference_config;
|
||||
}
|
||||
|
||||
// Transform tools if present
|
||||
if let Some(tools) = openai_req["tools"].as_array() {
|
||||
let mut bedrock_tools = Vec::new();
|
||||
|
||||
for tool in tools {
|
||||
if tool["type"].as_str() == Some("function") {
|
||||
if let Some(function) = tool["function"].as_object() {
|
||||
bedrock_tools.push(serde_json::json!({
|
||||
"toolSpec": {
|
||||
"name": function.get("name"),
|
||||
"description": function.get("description")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or("Tool function"),
|
||||
"inputSchema": {
|
||||
"json": function.get("parameters")
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !bedrock_tools.is_empty() {
|
||||
let mut tool_config = serde_json::json!({
|
||||
"tools": bedrock_tools
|
||||
});
|
||||
|
||||
// Transform tool_choice
|
||||
if let Some(tool_choice) = openai_req.get("tool_choice") {
|
||||
if tool_choice == "auto" {
|
||||
tool_config["toolChoice"] = serde_json::json!({"auto": {}});
|
||||
} else if tool_choice == "required" {
|
||||
tool_config["toolChoice"] = serde_json::json!({"any": {}});
|
||||
} else if let Some(obj) = tool_choice.as_object() {
|
||||
if obj.get("type").and_then(|v| v.as_str()) == Some("function") {
|
||||
if let Some(function) = obj.get("function").and_then(|v| v.as_object()) {
|
||||
if let Some(name) = function.get("name").and_then(|v| v.as_str()) {
|
||||
tool_config["toolChoice"] = serde_json::json!({
|
||||
"tool": {"name": name}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bedrock_req["toolConfig"] = tool_config;
|
||||
}
|
||||
}
|
||||
|
||||
let transformed_body = serde_json::to_vec(&bedrock_req)
|
||||
.map_err(|e| Error::internal_err(format!("Failed to serialize Bedrock request: {}", e)))?
|
||||
.into();
|
||||
|
||||
Ok((model, transformed_body, is_streaming))
|
||||
}
|
||||
|
||||
/// Transform AWS Bedrock Converse response to OpenAI format
|
||||
pub async fn transform_bedrock_to_openai(
|
||||
response: reqwest::Response,
|
||||
model: String,
|
||||
) -> Result<Bytes> {
|
||||
use serde_json::Value;
|
||||
|
||||
let bedrock_resp: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| Error::internal_err(format!("Failed to parse Bedrock response: {}", e)))?;
|
||||
|
||||
// Generate unique ID and timestamp
|
||||
let id = format!("chatcmpl-{}", uuid::Uuid::new_v4().simple());
|
||||
let created = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
// Extract stop reason and map to finish_reason
|
||||
let stop_reason = bedrock_resp["stopReason"].as_str().unwrap_or("end_turn");
|
||||
let finish_reason = match stop_reason {
|
||||
"end_turn" => "stop",
|
||||
"max_tokens" => "length",
|
||||
"tool_use" => "tool_calls",
|
||||
"stop_sequence" => "stop",
|
||||
"guardrail_intervened" | "content_filtered" => "content_filter",
|
||||
_ => "stop",
|
||||
};
|
||||
|
||||
// Extract message content
|
||||
let message_content = &bedrock_resp["output"]["message"]["content"];
|
||||
let mut text_content = String::new();
|
||||
let mut tool_calls = Vec::new();
|
||||
|
||||
if let Some(content_array) = message_content.as_array() {
|
||||
for (_index, block) in content_array.iter().enumerate() {
|
||||
if let Some(text) = block["text"].as_str() {
|
||||
text_content.push_str(text);
|
||||
} else if let Some(tool_use) = block.get("toolUse") {
|
||||
// Transform tool use to OpenAI tool_calls format
|
||||
let tool_call_id = tool_use["toolUseId"].as_str().unwrap_or("");
|
||||
let name = tool_use["name"].as_str().unwrap_or("");
|
||||
let input = &tool_use["input"];
|
||||
|
||||
tool_calls.push(serde_json::json!({
|
||||
"id": tool_call_id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": name,
|
||||
"arguments": serde_json::to_string(input).unwrap_or_default()
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the message
|
||||
let message = if !tool_calls.is_empty() {
|
||||
serde_json::json!({
|
||||
"role": "assistant",
|
||||
"content": if text_content.is_empty() { Value::Null } else { Value::String(text_content) },
|
||||
"tool_calls": tool_calls
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"role": "assistant",
|
||||
"content": text_content
|
||||
})
|
||||
};
|
||||
|
||||
// Extract usage information
|
||||
let usage = if let Some(usage_data) = bedrock_resp.get("usage") {
|
||||
serde_json::json!({
|
||||
"prompt_tokens": usage_data["inputTokens"].as_i64().unwrap_or(0),
|
||||
"completion_tokens": usage_data["outputTokens"].as_i64().unwrap_or(0),
|
||||
"total_tokens": usage_data["totalTokens"].as_i64().unwrap_or(0)
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0
|
||||
})
|
||||
};
|
||||
|
||||
// Build OpenAI-format response
|
||||
let openai_resp = serde_json::json!({
|
||||
"id": id,
|
||||
"object": "chat.completion",
|
||||
"created": created,
|
||||
"model": model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"message": message,
|
||||
"finish_reason": finish_reason
|
||||
}],
|
||||
"usage": usage
|
||||
});
|
||||
|
||||
let response_body = serde_json::to_vec(&openai_resp)
|
||||
.map_err(|e| Error::internal_err(format!("Failed to serialize OpenAI response: {}", e)))?
|
||||
.into();
|
||||
|
||||
Ok(response_body)
|
||||
}
|
||||
|
||||
/// Transform AWS Bedrock streaming response to OpenAI SSE format
|
||||
/// Bedrock uses AWS event stream binary format, not SSE
|
||||
pub fn transform_bedrock_stream_to_openai(
|
||||
stream: impl futures::Stream<Item = std::result::Result<bytes::Bytes, reqwest::Error>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
model: String,
|
||||
) -> impl futures::Stream<Item = std::result::Result<bytes::Bytes, std::io::Error>> + Send {
|
||||
use futures::stream::StreamExt;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let id = format!("chatcmpl-{}", uuid::Uuid::new_v4().simple());
|
||||
let created = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
// State to track partial tool calls and binary buffer
|
||||
struct StreamState {
|
||||
id: String,
|
||||
model: String,
|
||||
created: u64,
|
||||
tool_calls: HashMap<usize, (String, String, String)>, // index -> (id, name, args)
|
||||
buffer: Vec<u8>, // Binary buffer for AWS event stream
|
||||
}
|
||||
|
||||
let state = std::sync::Arc::new(tokio::sync::Mutex::new(StreamState {
|
||||
id: id.clone(),
|
||||
model: model.clone(),
|
||||
created,
|
||||
tool_calls: HashMap::new(),
|
||||
buffer: Vec::new(),
|
||||
}));
|
||||
|
||||
stream
|
||||
.then(move |chunk_result| {
|
||||
let state = state.clone();
|
||||
async move {
|
||||
match chunk_result {
|
||||
Ok(chunk) => {
|
||||
let mut state = state.lock().await;
|
||||
state.buffer.extend_from_slice(&chunk);
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
// Parse AWS event stream messages from buffer
|
||||
loop {
|
||||
// Need at least 12 bytes for prelude (8) + prelude CRC (4)
|
||||
if state.buffer.len() < 12 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Read prelude: total_length (4 bytes) + headers_length (4 bytes)
|
||||
let total_length = u32::from_be_bytes([
|
||||
state.buffer[0],
|
||||
state.buffer[1],
|
||||
state.buffer[2],
|
||||
state.buffer[3],
|
||||
]) as usize;
|
||||
|
||||
// Check if we have the complete message
|
||||
if state.buffer.len() < total_length {
|
||||
break;
|
||||
}
|
||||
|
||||
let headers_length = u32::from_be_bytes([
|
||||
state.buffer[4],
|
||||
state.buffer[5],
|
||||
state.buffer[6],
|
||||
state.buffer[7],
|
||||
]) as usize;
|
||||
|
||||
// Skip prelude CRC (4 bytes after prelude)
|
||||
let headers_start = 12;
|
||||
let payload_start = headers_start + headers_length;
|
||||
let payload_end = total_length - 4; // Exclude message CRC
|
||||
|
||||
// Parse headers to extract event type
|
||||
let mut event_type = None;
|
||||
let mut pos = headers_start;
|
||||
while pos < payload_start {
|
||||
if pos + 1 > state.buffer.len() {
|
||||
break;
|
||||
}
|
||||
let name_len = state.buffer[pos] as usize;
|
||||
pos += 1;
|
||||
|
||||
if pos + name_len > state.buffer.len() {
|
||||
break;
|
||||
}
|
||||
let name = String::from_utf8_lossy(&state.buffer[pos..pos + name_len]).to_string();
|
||||
pos += name_len;
|
||||
|
||||
if pos + 3 > state.buffer.len() {
|
||||
break;
|
||||
}
|
||||
let value_type = state.buffer[pos];
|
||||
pos += 1;
|
||||
let value_len = u16::from_be_bytes([state.buffer[pos], state.buffer[pos + 1]]) as usize;
|
||||
pos += 2;
|
||||
|
||||
if pos + value_len > state.buffer.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
if value_type == 7 && name == ":event-type" {
|
||||
event_type = Some(String::from_utf8_lossy(&state.buffer[pos..pos + value_len]).to_string());
|
||||
}
|
||||
pos += value_len;
|
||||
}
|
||||
|
||||
// Extract JSON payload (copy to avoid borrow issues)
|
||||
let payload = state.buffer[payload_start..payload_end].to_vec();
|
||||
|
||||
// Remove processed message from buffer
|
||||
state.buffer.drain(0..total_length);
|
||||
|
||||
// Process the event
|
||||
if let Some(evt_type) = event_type {
|
||||
if let Ok(payload_str) = std::str::from_utf8(&payload) {
|
||||
if let Ok(parsed_data) = serde_json::from_str::<Value>(payload_str) {
|
||||
// Transform based on event type
|
||||
match evt_type.as_str() {
|
||||
"messageStart" => {
|
||||
// No output for messageStart
|
||||
}
|
||||
"contentBlockStart" => {
|
||||
let index = parsed_data["contentBlockIndex"].as_u64().unwrap_or(0) as usize;
|
||||
|
||||
if let Some(tool_use) = parsed_data["start"].get("toolUse") {
|
||||
let tool_id = tool_use["toolUseId"].as_str().unwrap_or("").to_string();
|
||||
let name = tool_use["name"].as_str().unwrap_or("").to_string();
|
||||
|
||||
state.tool_calls.insert(index, (tool_id.clone(), name.clone(), String::new()));
|
||||
|
||||
// Send initial tool call chunk
|
||||
let chunk = serde_json::json!({
|
||||
"id": state.id,
|
||||
"object": "chat.completion.chunk",
|
||||
"created": state.created,
|
||||
"model": state.model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {
|
||||
"tool_calls": [{
|
||||
"index": index,
|
||||
"id": tool_id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": name,
|
||||
"arguments": ""
|
||||
}
|
||||
}]
|
||||
},
|
||||
"finish_reason": Value::Null
|
||||
}]
|
||||
});
|
||||
|
||||
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
|
||||
}
|
||||
}
|
||||
"contentBlockDelta" => {
|
||||
let index = parsed_data["contentBlockIndex"].as_u64().unwrap_or(0) as usize;
|
||||
|
||||
if let Some(text) = parsed_data["delta"]["text"].as_str() {
|
||||
// Text content delta
|
||||
let chunk = serde_json::json!({
|
||||
"id": state.id,
|
||||
"object": "chat.completion.chunk",
|
||||
"created": state.created,
|
||||
"model": state.model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {
|
||||
"content": text
|
||||
},
|
||||
"finish_reason": Value::Null
|
||||
}]
|
||||
});
|
||||
|
||||
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
|
||||
} else if let Some(tool_use_input) = parsed_data["delta"]["toolUse"]["input"].as_str() {
|
||||
// Tool use arguments delta
|
||||
if let Some((_tool_id, _name, ref mut args)) = state.tool_calls.get_mut(&index) {
|
||||
args.push_str(tool_use_input);
|
||||
|
||||
let chunk = serde_json::json!({
|
||||
"id": state.id,
|
||||
"object": "chat.completion.chunk",
|
||||
"created": state.created,
|
||||
"model": state.model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {
|
||||
"tool_calls": [{
|
||||
"index": index,
|
||||
"function": {
|
||||
"arguments": tool_use_input
|
||||
}
|
||||
}]
|
||||
},
|
||||
"finish_reason": Value::Null
|
||||
}]
|
||||
});
|
||||
|
||||
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
|
||||
}
|
||||
}
|
||||
}
|
||||
"contentBlockStop" => {
|
||||
// No output needed
|
||||
}
|
||||
"messageStop" => {
|
||||
let stop_reason = parsed_data["stopReason"].as_str().unwrap_or("end_turn");
|
||||
let finish_reason = match stop_reason {
|
||||
"end_turn" => "stop",
|
||||
"max_tokens" => "length",
|
||||
"tool_use" => "tool_calls",
|
||||
"stop_sequence" => "stop",
|
||||
"guardrail_intervened" | "content_filtered" => "content_filter",
|
||||
_ => "stop",
|
||||
};
|
||||
|
||||
let chunk = serde_json::json!({
|
||||
"id": state.id,
|
||||
"object": "chat.completion.chunk",
|
||||
"created": state.created,
|
||||
"model": state.model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {},
|
||||
"finish_reason": finish_reason
|
||||
}]
|
||||
});
|
||||
|
||||
events.push(Ok(bytes::Bytes::from(format!("data: {}\n\n", chunk))));
|
||||
}
|
||||
"metadata" => {
|
||||
// Could include usage info here if needed
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end loop
|
||||
|
||||
events
|
||||
}
|
||||
Err(e) => {
|
||||
vec![Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
e.to_string(),
|
||||
))]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.flat_map(|events| futures::stream::iter(events))
|
||||
.chain(futures::stream::iter(vec![
|
||||
// Send [DONE] at the end
|
||||
Ok(bytes::Bytes::from("data: [DONE]\n\n"))
|
||||
]))
|
||||
}
|
||||
@@ -222,6 +222,7 @@ async fn get_concurrent_intervals(
|
||||
all_workspaces: _,
|
||||
concurrency_key: Some(_),
|
||||
allow_wildcards: None,
|
||||
trigger_kind: _,
|
||||
} => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -53,6 +53,9 @@ lazy_static::lazy_static! {
|
||||
).to_string()),
|
||||
(20221105003256, "DELETE FROM workspace_invite WHERE workspace_id = 'demo' AND email = 'ruben@windmill.dev';".to_string()),
|
||||
(20221123151919, "".to_string()),
|
||||
(20251105100125, include_str!(
|
||||
"../../migrations/20251105100125_legacy_sql_result_flag.up.sql"
|
||||
).replace("✅", "")),
|
||||
].into_iter().collect();
|
||||
}
|
||||
|
||||
@@ -166,6 +169,7 @@ impl Migrate for CustomMigrator {
|
||||
|
||||
if let Some(migration_sql) = OVERRIDDEN_MIGRATIONS.get(&migration.version) {
|
||||
tracing::info!("Using custom migration for version {}", migration.version);
|
||||
// tracing::info!("Migration SQL: {}", migration_sql);
|
||||
|
||||
self.inner
|
||||
.execute(&**migration_sql)
|
||||
|
||||
@@ -13,7 +13,10 @@ use anyhow::anyhow;
|
||||
#[cfg(all(feature = "enterprise", not(feature = "private")))]
|
||||
use {std::sync::Arc, tokio::sync::RwLock};
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_license_key(_license_key: String) -> anyhow::Result<(String, bool)> {
|
||||
pub async fn validate_license_key(
|
||||
_license_key: String,
|
||||
_db: Option<&crate::db::DB>,
|
||||
) -> anyhow::Result<(String, bool)> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
@@ -23,6 +26,7 @@ pub async fn jwt_ext_auth(
|
||||
_w_id: Option<&String>,
|
||||
_token: &str,
|
||||
_external_jwks: Option<Arc<RwLock<ExternalJwks>>>,
|
||||
_db: &crate::db::DB,
|
||||
) -> anyhow::Result<(crate::db::ApiAuthed, usize)> {
|
||||
// Implementation is not open source
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
db::{UserDB, DB},
|
||||
error::{JsonResult, Result},
|
||||
flow_conversations::MessageType,
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
@@ -154,6 +154,7 @@ pub async fn get_or_create_conversation_with_id(
|
||||
async fn delete_conversation(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
@@ -185,10 +186,14 @@ async fn delete_conversation(
|
||||
|
||||
// Delete associated memory in background (non-blocking cleanup)
|
||||
let w_id_clone = w_id.clone();
|
||||
let db_clone = db.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) =
|
||||
windmill_worker::memory_oss::delete_conversation_memory(&w_id_clone, conversation_id)
|
||||
.await
|
||||
if let Err(e) = windmill_worker::memory_oss::delete_conversation_memory(
|
||||
&db_clone,
|
||||
&w_id_clone,
|
||||
conversation_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to delete memory for conversation {} in workspace {}: {:?}",
|
||||
|
||||
@@ -562,6 +562,7 @@ async fn create_flow(
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1025,6 +1026,7 @@ async fn update_flow(
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1133,6 +1135,7 @@ async fn get_flow_by_path(
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
@@ -1172,8 +1175,9 @@ async fn get_flow_by_path(
|
||||
flow.timeout,
|
||||
flow.visible_to_runner_only,
|
||||
flow.on_behalf_of_email,
|
||||
flow_version.id AS version_id,
|
||||
flow_version.schema,
|
||||
flow_version.value,
|
||||
flow_version.value,
|
||||
flow_version.created_at AS edited_at,
|
||||
flow_version.created_by AS edited_by,
|
||||
NULL AS starred
|
||||
@@ -1634,6 +1638,7 @@ mod tests {
|
||||
early_return: None,
|
||||
concurrency_key: None,
|
||||
chat_input_enabled: None,
|
||||
flow_env: None,
|
||||
debounce_key: None,
|
||||
debounce_delay_s: None,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,10 @@ use tower_http::{
|
||||
};
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{utils::{configure_client, GIT_VERSION}, BASE_URL, INSTANCE_NAME};
|
||||
use windmill_common::{
|
||||
utils::{configure_client, GIT_VERSION},
|
||||
BASE_URL, INSTANCE_NAME,
|
||||
};
|
||||
|
||||
use crate::scim_oss::has_scim_token;
|
||||
use windmill_common::error::AppError;
|
||||
@@ -77,6 +80,7 @@ pub mod args;
|
||||
mod assets;
|
||||
mod audit;
|
||||
pub mod auth;
|
||||
mod bedrock;
|
||||
mod capture;
|
||||
mod concurrency_groups;
|
||||
mod configs;
|
||||
@@ -151,6 +155,7 @@ mod smtp_server_oss;
|
||||
pub mod teams_approvals_ee;
|
||||
mod teams_approvals_oss;
|
||||
|
||||
mod public_app_layer;
|
||||
mod static_assets;
|
||||
#[cfg(all(feature = "stripe", feature = "enterprise", feature = "private"))]
|
||||
pub mod stripe_ee;
|
||||
@@ -320,6 +325,11 @@ pub async fn run_server(
|
||||
#[cfg(feature = "embedding")]
|
||||
load_embeddings_db(&db);
|
||||
|
||||
#[cfg(feature = "cloud")]
|
||||
if *CLOUD_HOSTED {
|
||||
windmill_queue::init_usage_buffer(db.clone());
|
||||
}
|
||||
|
||||
let mut start_smtp_server = false;
|
||||
if let Some(smtp_settings) =
|
||||
load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await?
|
||||
@@ -699,6 +709,15 @@ pub async fn run_server(
|
||||
)
|
||||
};
|
||||
|
||||
let app = if let Some(domain) = public_app_layer::PUBLIC_APP_DOMAIN.as_ref() {
|
||||
tracing::info!("Public app domain filter enabled for domain: {}", domain);
|
||||
app.layer(axum::middleware::from_fn(
|
||||
public_app_layer::public_app_domain_filter,
|
||||
))
|
||||
} else {
|
||||
app
|
||||
};
|
||||
|
||||
let app = app.layer(CatchPanicLayer::custom(|err| {
|
||||
tracing::error!("panic in handler, returning 500: {:?}", err);
|
||||
Response::builder()
|
||||
|
||||
@@ -39,6 +39,7 @@ use super::utils::{
|
||||
FlowInfo, ResourceInfo, ResourceType, SchemaType, ScriptInfo, ToolableItem, WorkspaceId,
|
||||
},
|
||||
schema::transform_schema_for_resources,
|
||||
scope_matcher::{is_resource_allowed, parse_mcp_scopes},
|
||||
transform::{reverse_transform, reverse_transform_key},
|
||||
};
|
||||
|
||||
@@ -151,6 +152,10 @@ impl ServerHandler for Runner {
|
||||
|
||||
check_scopes(authed)?;
|
||||
|
||||
// Parse MCP scopes for authorization
|
||||
let scopes = authed.scopes.as_ref().map(|s| s.as_slice()).unwrap_or(&[]);
|
||||
let scope_config = parse_mcp_scopes(scopes)?;
|
||||
|
||||
if request.name.ends_with("_TRUNC") {
|
||||
return Ok(CallToolResult::error(
|
||||
vec![
|
||||
@@ -197,6 +202,19 @@ impl ServerHandler for Runner {
|
||||
let endpoint_tools = all_endpoint_tools();
|
||||
for endpoint_tool in endpoint_tools {
|
||||
if endpoint_tool.name.as_ref() == request.name {
|
||||
// Validate endpoint scope
|
||||
if scope_config.granular
|
||||
&& !is_resource_allowed(&endpoint_tool.name, &scope_config.endpoints)
|
||||
{
|
||||
return Err(ErrorData::internal_error(
|
||||
format!(
|
||||
"Access denied: endpoint '{}' not in token scope",
|
||||
endpoint_tool.name
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
// This is an endpoint tool, forward to the actual HTTP endpoint
|
||||
let result =
|
||||
call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed)
|
||||
@@ -212,6 +230,21 @@ impl ServerHandler for Runner {
|
||||
ErrorData::internal_error(format!("Failed to reverse transform path: {}", e), None)
|
||||
})?;
|
||||
|
||||
// Validate script/flow scope
|
||||
if !is_hub && scope_config.granular {
|
||||
if tool_type == "script" && !is_resource_allowed(&path, &scope_config.scripts) {
|
||||
return Err(ErrorData::internal_error(
|
||||
format!("Access denied: script '{}' not in token scope", path),
|
||||
None,
|
||||
));
|
||||
} else if tool_type == "flow" && !is_resource_allowed(&path, &scope_config.flows) {
|
||||
return Err(ErrorData::internal_error(
|
||||
format!("Access denied: flow '{}' not in token scope", path),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let item_schema = if is_hub {
|
||||
get_hub_script_schema(&format!("hub/{}", path), db).await?
|
||||
} else {
|
||||
@@ -337,53 +370,23 @@ impl ServerHandler for Runner {
|
||||
})
|
||||
.map(|w_id| w_id.0.clone())?;
|
||||
|
||||
let scopes = authed.scopes.as_ref();
|
||||
let owned_scope = scopes.and_then(|scopes| {
|
||||
scopes
|
||||
.iter()
|
||||
.find(|scope| scope.starts_with("mcp:") && !scope.contains("hub"))
|
||||
});
|
||||
let hub_scope =
|
||||
scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub")));
|
||||
let (scope_type, scope_path) = owned_scope.map_or(("all", None), |scope| {
|
||||
let parts = scope.split(":").collect::<Vec<&str>>();
|
||||
(
|
||||
parts[1],
|
||||
if parts.len() == 3 {
|
||||
Some(parts[2])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
});
|
||||
let scope_integrations = hub_scope.and_then(|scope| {
|
||||
let parts = scope.split(":").collect::<Vec<&str>>();
|
||||
if parts.len() == 3 {
|
||||
Some(parts[2])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// Parse MCP scopes to determine what to expose
|
||||
let scopes = authed.scopes.as_ref().map(|s| s.as_slice()).unwrap_or(&[]);
|
||||
let scope_config = parse_mcp_scopes(scopes)?;
|
||||
|
||||
let scripts_fn = get_items::<ScriptInfo>(
|
||||
user_db,
|
||||
authed,
|
||||
&workspace_id,
|
||||
scope_type,
|
||||
"script",
|
||||
scope_path.as_deref(),
|
||||
);
|
||||
let flows_fn = get_items::<FlowInfo>(
|
||||
user_db,
|
||||
authed,
|
||||
&workspace_id,
|
||||
scope_type,
|
||||
"flow",
|
||||
scope_path.as_deref(),
|
||||
);
|
||||
let scope_type = if scope_config.favorites {
|
||||
"favorites"
|
||||
} else {
|
||||
// Fetch all items if either all or granular scope set (we filter later for granular scopes)
|
||||
"all"
|
||||
};
|
||||
|
||||
let scripts_fn =
|
||||
get_items::<ScriptInfo>(user_db, authed, &workspace_id, scope_type, "script");
|
||||
let flows_fn = get_items::<FlowInfo>(user_db, authed, &workspace_id, scope_type, "flow");
|
||||
let resources_types_fn = get_resources_types(user_db, authed, &workspace_id);
|
||||
let hub_scripts_fn = get_scripts_from_hub(db, scope_integrations.as_deref());
|
||||
let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() {
|
||||
let hub_scripts_fn = get_scripts_from_hub(db, scope_config.hub_apps.as_deref());
|
||||
let (scripts, flows, resources_types, hub_scripts) = if scope_config.hub_apps.is_some() {
|
||||
let (scripts, flows, resources_types, hub_scripts) =
|
||||
try_join!(scripts_fn, flows_fn, resources_types_fn, hub_scripts_fn)?;
|
||||
(scripts, flows, resources_types, hub_scripts)
|
||||
@@ -396,7 +399,13 @@ impl ServerHandler for Runner {
|
||||
let mut resources_cache: HashMap<String, Vec<ResourceInfo>> = HashMap::new();
|
||||
let mut tools: Vec<Tool> = Vec::new();
|
||||
|
||||
// Filter and add scripts based on scope
|
||||
for script in scripts {
|
||||
// For granular scopes, filter by path
|
||||
if scope_config.granular && !is_resource_allowed(&script.path, &scope_config.scripts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tools.push(
|
||||
Runner::create_tool_from_item(
|
||||
&script,
|
||||
@@ -410,7 +419,13 @@ impl ServerHandler for Runner {
|
||||
);
|
||||
}
|
||||
|
||||
// Filter and add flows based on scope
|
||||
for flow in flows {
|
||||
// For granular scopes, filter by path
|
||||
if scope_config.granular && !is_resource_allowed(&flow.path, &scope_config.flows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tools.push(
|
||||
Runner::create_tool_from_item(
|
||||
&flow,
|
||||
@@ -438,10 +453,23 @@ impl ServerHandler for Runner {
|
||||
);
|
||||
}
|
||||
|
||||
// Add endpoint tools from the generated MCP tools
|
||||
// Add endpoint tools from the generated MCP tools, filtered by scope
|
||||
let endpoint_tools = all_endpoint_tools();
|
||||
let mcp_tools_converted = endpoint_tools_to_mcp_tools(endpoint_tools);
|
||||
tools.extend(mcp_tools_converted);
|
||||
for endpoint_tool in endpoint_tools {
|
||||
// For granular scopes, filter by endpoint name
|
||||
if scope_config.granular
|
||||
&& !is_resource_allowed(&endpoint_tool.name, &scope_config.endpoints)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tools.push(
|
||||
endpoint_tools_to_mcp_tools(vec![endpoint_tool])
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(ListToolsResult { tools, next_cursor: None })
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ use crate::HTTP_CLIENT;
|
||||
pub fn check_scopes(authed: &ApiAuthed) -> Result<(), ErrorData> {
|
||||
let scopes = authed.scopes.as_ref();
|
||||
if scopes.is_none()
|
||||
|| scopes.unwrap().iter().all(|scope| {
|
||||
!scope.starts_with("mcp:all")
|
||||
&& !scope.starts_with("mcp:favorites")
|
||||
&& !scope.starts_with("mcp:hub:")
|
||||
})
|
||||
|| scopes
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|scope| !scope.starts_with("mcp:"))
|
||||
{
|
||||
tracing::error!("Unauthorized: missing mcp scope");
|
||||
return Err(ErrorData::internal_error(
|
||||
@@ -141,7 +140,6 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
workspace_id: &str,
|
||||
scope_type: &str,
|
||||
item_type: &str,
|
||||
scope_path: Option<&str>,
|
||||
) -> Result<Vec<T>, ErrorData> {
|
||||
let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type));
|
||||
let fields = vec!["o.path", "o.summary", "o.description", "o.schema"];
|
||||
@@ -159,23 +157,6 @@ pub async fn get_items<T: for<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> + Sen
|
||||
sqlb.and_where("(o.no_main_func IS NOT TRUE OR o.no_main_func IS NULL)");
|
||||
}
|
||||
|
||||
// scope path is always a folder path, format is f/my_folder/*
|
||||
if let Some(scope_path) = scope_path {
|
||||
if scope_path.split("/").count() != 3
|
||||
|| !scope_path.starts_with("f/")
|
||||
|| !scope_path.ends_with("/*")
|
||||
{
|
||||
return Err(ErrorData::internal_error(
|
||||
format!(
|
||||
"Invalid folder format: {}, expected format is f/my_folder/*",
|
||||
scope_path
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
sqlb.and_where_like_left("o.path", &scope_path[..scope_path.len() - 2]);
|
||||
}
|
||||
|
||||
sqlb.order_by(
|
||||
if item_type == "flow" {
|
||||
"o.edited_at"
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
pub mod models;
|
||||
pub mod database;
|
||||
pub mod schema;
|
||||
pub mod transform;
|
||||
pub mod transform;
|
||||
pub mod scope_matcher;
|
||||
229
backend/windmill-api/src/mcp/utils/scope_matcher.rs
Normal file
229
backend/windmill-api/src/mcp/utils/scope_matcher.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
//! MCP Scope matching utilities
|
||||
//!
|
||||
//! Contains utilities for parsing and matching MCP token scopes to determine
|
||||
//! which scripts, flows, and endpoints a token has access to.
|
||||
|
||||
use rmcp::ErrorData;
|
||||
|
||||
/// Configuration for MCP scopes parsed from token scopes
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct McpScopeConfig {
|
||||
/// Script paths/patterns allowed by this token
|
||||
pub scripts: Vec<String>,
|
||||
/// Flow paths/patterns allowed by this token
|
||||
pub flows: Vec<String>,
|
||||
/// Endpoint names/patterns allowed by this token
|
||||
pub endpoints: Vec<String>,
|
||||
/// Whether this is a legacy "all" scope
|
||||
pub all: bool,
|
||||
/// Whether this is a "favorites" scope
|
||||
pub favorites: bool,
|
||||
/// Whether a granular scope is detected
|
||||
pub granular: bool,
|
||||
/// Hub app filter (if any)
|
||||
pub hub_apps: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse MCP scopes from token scope strings
|
||||
pub fn parse_mcp_scopes(scopes: &[String]) -> Result<McpScopeConfig, ErrorData> {
|
||||
let mut config = McpScopeConfig::default();
|
||||
|
||||
for scope in scopes {
|
||||
if !scope.starts_with("mcp:") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if scope == "mcp:all" {
|
||||
// Legacy scope: grant access to everything
|
||||
config.all = true;
|
||||
config.scripts.push("*".to_string());
|
||||
config.flows.push("*".to_string());
|
||||
config.endpoints.push("*".to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if scope == "mcp:favorites" {
|
||||
// Legacy favorites scope
|
||||
config.favorites = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Legacy folder scope: mcp:all:f/folder/*
|
||||
if scope.starts_with("mcp:all:") {
|
||||
if let Some(folder_pattern) = scope.strip_prefix("mcp:all:") {
|
||||
// Parse as folder pattern - add to both scripts and flows. Also add all endpoints.
|
||||
config.scripts.push(folder_pattern.to_string());
|
||||
config.flows.push(folder_pattern.to_string());
|
||||
config.endpoints.push("*".to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if scope.starts_with("mcp:hub:") {
|
||||
// Legacy hub scope
|
||||
if let Some(apps) = scope.strip_prefix("mcp:hub:") {
|
||||
config.hub_apps = Some(apps.to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(resources) = scope.strip_prefix("mcp:scripts:") {
|
||||
// New granular script scope: mcp:scripts:path1,path2,f/folder/*
|
||||
config.scripts.extend(parse_resource_list(resources)?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(resources) = scope.strip_prefix("mcp:flows:") {
|
||||
// New granular flow scope: mcp:flows:path1,path2,f/folder/*
|
||||
config.flows.extend(parse_resource_list(resources)?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(resources) = scope.strip_prefix("mcp:endpoints:") {
|
||||
// New granular endpoint scope: mcp:endpoints:name1,name2
|
||||
config.endpoints.extend(parse_resource_list(resources)?);
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::warn!("Unrecognized MCP scope format: {}", scope);
|
||||
}
|
||||
|
||||
config.granular = !config.all && !config.favorites;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Parse comma-separated resource list
|
||||
fn parse_resource_list(resources: &str) -> Result<Vec<String>, ErrorData> {
|
||||
if resources.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
Ok(resources
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Check if a resource path matches any pattern in the allowed list
|
||||
pub fn is_resource_allowed(resource_path: &str, allowed_patterns: &[String]) -> bool {
|
||||
if allowed_patterns.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wildcard grants all access
|
||||
if allowed_patterns.contains(&"*".to_string()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check against each pattern
|
||||
for pattern in allowed_patterns {
|
||||
if resource_matches_pattern(resource_path, pattern) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a resource path matches a pattern (supports wildcards like f/folder/*)
|
||||
fn resource_matches_pattern(resource_path: &str, pattern: &str) -> bool {
|
||||
// Exact match
|
||||
if pattern == resource_path {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wildcard pattern matching
|
||||
if pattern.ends_with("/*") {
|
||||
let prefix = &pattern[..pattern.len() - 2];
|
||||
|
||||
if !resource_path.starts_with(prefix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the resource is exactly the prefix, it matches
|
||||
if resource_path.len() == prefix.len() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the resource is longer, the next character must be '/' for a valid match
|
||||
// This prevents "u/user" from matching "u/use/*"
|
||||
return resource_path.chars().nth(prefix.len()) == Some('/');
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_legacy_scopes() {
|
||||
let scopes = vec!["mcp:all".to_string()];
|
||||
let config = parse_mcp_scopes(&scopes).unwrap();
|
||||
assert!(config.all);
|
||||
assert_eq!(config.scripts, vec!["*"]);
|
||||
assert_eq!(config.flows, vec!["*"]);
|
||||
assert_eq!(config.endpoints, vec!["*"]);
|
||||
|
||||
let scopes = vec!["mcp:favorites".to_string()];
|
||||
let config = parse_mcp_scopes(&scopes).unwrap();
|
||||
assert!(config.favorites);
|
||||
|
||||
let scopes = vec!["mcp:hub:slack".to_string()];
|
||||
let config = parse_mcp_scopes(&scopes).unwrap();
|
||||
assert_eq!(config.hub_apps, Some("slack".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_granular_scopes() {
|
||||
let scopes = vec![
|
||||
"mcp:scripts:u/admin/script1,u/admin/script2".to_string(),
|
||||
"mcp:flows:f/automation/*".to_string(),
|
||||
"mcp:endpoints:list_jobs,get_job".to_string(),
|
||||
];
|
||||
let config = parse_mcp_scopes(&scopes).unwrap();
|
||||
|
||||
assert_eq!(config.scripts, vec!["u/admin/script1", "u/admin/script2"]);
|
||||
assert_eq!(config.flows, vec!["f/automation/*"]);
|
||||
assert_eq!(config.endpoints, vec!["list_jobs", "get_job"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_matching() {
|
||||
// Exact match
|
||||
assert!(resource_matches_pattern("u/admin/script", "u/admin/script"));
|
||||
|
||||
// Wildcard folder match
|
||||
assert!(resource_matches_pattern("f/folder/script", "f/folder/*"));
|
||||
assert!(resource_matches_pattern(
|
||||
"f/folder/sub/script",
|
||||
"f/folder/*"
|
||||
));
|
||||
|
||||
// Should NOT match - prefix is not complete
|
||||
assert!(!resource_matches_pattern("u/username", "u/user/*"));
|
||||
|
||||
// Should match - exact prefix
|
||||
assert!(resource_matches_pattern("u/user/script", "u/user/*"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_resource_allowed() {
|
||||
let patterns = vec!["u/admin/script1".to_string(), "f/folder/*".to_string()];
|
||||
|
||||
assert!(is_resource_allowed("u/admin/script1", &patterns));
|
||||
assert!(is_resource_allowed("f/folder/anything", &patterns));
|
||||
assert!(!is_resource_allowed("u/other/script", &patterns));
|
||||
|
||||
// Test wildcard
|
||||
let wildcard = vec!["*".to_string()];
|
||||
assert!(is_resource_allowed("any/path", &wildcard));
|
||||
|
||||
// Test empty patterns
|
||||
let empty: Vec<String> = vec![];
|
||||
assert!(!is_resource_allowed("any/path", &empty));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user