Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36aadbebec | ||
|
|
bedba3b75e | ||
|
|
771d67c849 | ||
|
|
46d486960a | ||
|
|
ca73267cbb | ||
|
|
6eabb8db63 | ||
|
|
5cc8f20cd2 | ||
|
|
ea5312e940 | ||
|
|
c62ba73ce4 | ||
|
|
98ac164ac8 | ||
|
|
ed6aaeeea3 | ||
|
|
5e415c0a12 | ||
|
|
35dded1347 | ||
|
|
aedf012c84 | ||
|
|
62fea97547 | ||
|
|
96c2d88d91 | ||
|
|
5eb308ad35 | ||
|
|
cfa04e1188 | ||
|
|
0d520f730b | ||
|
|
8cdc7d6e9e | ||
|
|
51077245c7 | ||
|
|
9e387c3559 | ||
|
|
7aea965803 | ||
|
|
8446e3b551 | ||
|
|
a6d6136d57 | ||
|
|
c93b2e287c | ||
|
|
93f927c1c1 | ||
|
|
83f21510f3 | ||
|
|
5c1f69ddcd | ||
|
|
3f2bd424c7 | ||
|
|
0d5f42e89e | ||
|
|
dfad07881d | ||
|
|
7b6ba7093a | ||
|
|
8042e33c38 | ||
|
|
57d23c92c5 | ||
|
|
f21140f7cd | ||
|
|
2800226bd4 | ||
|
|
2ae82796fc | ||
|
|
d1290ba777 | ||
|
|
9de0060884 | ||
|
|
3df8964fc6 | ||
|
|
fb0b2234ba | ||
|
|
4064ec3a3d | ||
|
|
0db6cbd10c | ||
|
|
ab91f78017 | ||
|
|
1e0245ca9a | ||
|
|
bead746bb8 | ||
|
|
268b5ee2a8 | ||
|
|
24f2571b37 | ||
|
|
ed1a655317 | ||
|
|
a4b440a20d | ||
|
|
b550be8711 | ||
|
|
071129f03b | ||
|
|
e9ac1ce9eb | ||
|
|
587142ddac | ||
|
|
d9ed0c318f | ||
|
|
4959a0553a | ||
|
|
c4323e40c1 | ||
|
|
696b8de1ed | ||
|
|
5f6dda9060 | ||
|
|
8490f4435d | ||
|
|
fa2f65e512 | ||
|
|
b9dec43d2a | ||
|
|
5227b76c2f | ||
|
|
1643d654a8 | ||
|
|
65a8789dfd | ||
|
|
6299e7a36a | ||
|
|
bcbfe4659d | ||
|
|
b2fac069df | ||
|
|
4ab08cb6a1 | ||
|
|
d6d4d85d8f | ||
|
|
81d386d365 | ||
|
|
a1b878842f | ||
|
|
00c3e9baf0 | ||
|
|
118dcb59af | ||
|
|
bd583be239 | ||
|
|
c1f7cb5d42 | ||
|
|
d502ef5029 | ||
|
|
bc7ca9982b |
@@ -100,4 +100,4 @@
|
||||
"typescript-lsp@claude-plugins-official": true,
|
||||
"code-review@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,93 +226,4 @@ When generating Svelte 5 code, prioritize frontend performance by applying the f
|
||||
Hello
|
||||
</div>
|
||||
```
|
||||
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
|
||||
|
||||
## Windmill UI Component Rules (MUST follow)
|
||||
|
||||
Always use Windmill's own design-system components instead of raw HTML elements. Using raw HTML elements produces inconsistent styling and breaks the design language.
|
||||
|
||||
### Icons — use `lucide-svelte`
|
||||
|
||||
**Never** write inline SVGs. Import icons from `lucide-svelte`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { ChevronLeft, ChevronRight, X } from 'lucide-svelte'
|
||||
</script>
|
||||
|
||||
<ChevronLeft size={16} />
|
||||
```
|
||||
|
||||
### Buttons — use `<Button>`
|
||||
|
||||
**Never** use `<button>`. Import and use `Button` from `$lib/components/common`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Button } from '$lib/components/common'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
|
||||
</script>
|
||||
|
||||
<!-- Regular button -->
|
||||
<Button variant="default" onclick={handleClick}>Label</Button>
|
||||
|
||||
<!-- Icon-only button (no label) -->
|
||||
<Button startIcon={{ icon: ChevronLeft }} iconOnly onclick={prevMonth} />
|
||||
<Button startIcon={{ icon: ChevronRight }} iconOnly onclick={nextMonth} />
|
||||
```
|
||||
|
||||
Key `Button` props:
|
||||
- `variant?: 'accent' | 'accent-secondary' | 'default' | 'subtle'`
|
||||
- `unifiedSize?: 'sm' | 'md' | 'lg'`
|
||||
- `startIcon?: { icon: SvelteComponent }` — renders an icon before the label
|
||||
- `iconOnly?: boolean` — renders icon with no surrounding label text
|
||||
- `disabled?: boolean`
|
||||
|
||||
### Text inputs — use `<TextInput>`
|
||||
|
||||
**Never** use `<input>`. Import and use `TextInput` from `$lib/components/common`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { TextInput } from '$lib/components/common'
|
||||
let val = $state('')
|
||||
</script>
|
||||
|
||||
<TextInput bind:value={val} placeholder="Enter value" />
|
||||
```
|
||||
|
||||
Key `TextInput` props:
|
||||
- `value?: string | number` (bindable)
|
||||
- `placeholder?: string`
|
||||
- `disabled?: boolean`
|
||||
- `error?: string | boolean`
|
||||
- `size?: 'sm' | 'md' | 'lg'`
|
||||
- `inputProps?` — forwarded to the underlying `<input>`
|
||||
|
||||
### Selects — use `<Select>`
|
||||
|
||||
**Never** use `<select>`. Import and use `Select` from `$lib/components/select/Select.svelte`.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import Select from '$lib/components/select/Select.svelte'
|
||||
|
||||
const monthItems = [
|
||||
{ label: 'January', value: 1 },
|
||||
{ label: 'February', value: 2 },
|
||||
// ...
|
||||
]
|
||||
let selectedMonth = $state(1)
|
||||
</script>
|
||||
|
||||
<Select items={monthItems} bind:value={selectedMonth} />
|
||||
```
|
||||
|
||||
Key `Select` props:
|
||||
- `items?: Array<{ label?: string; value: any; subtitle?: string; disabled?: boolean }>`
|
||||
- `value` (bindable) — the currently selected `.value`
|
||||
- `placeholder?: string`
|
||||
- `clearable?: boolean`
|
||||
- `disabled?: boolean`
|
||||
- `size?: 'sm' | 'md' | 'lg'`
|
||||
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
|
||||
21
.github/workflows/backend-test.yml
vendored
21
.github/workflows/backend-test.yml
vendored
@@ -19,7 +19,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
cargo_test:
|
||||
runs-on: ubicloud-standard-16
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -86,8 +86,22 @@ jobs:
|
||||
working-directory: /
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache-workspaces: backend
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
- name: Cache cargo target directory
|
||||
uses: useblacksmith/stickydisk@v1
|
||||
with:
|
||||
key: cargo-target
|
||||
path: ./backend/target
|
||||
- name: Cache cargo registry
|
||||
uses: useblacksmith/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-registry-${{ hashFiles('backend/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-registry-
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
@@ -215,7 +229,7 @@ jobs:
|
||||
fi
|
||||
echo "Verified: Package requires authentication for @windmill-test/private-pkg"
|
||||
- name: Cache DuckDB FFI module build
|
||||
uses: actions/cache@v3
|
||||
uses: useblacksmith/cache@v1
|
||||
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') }}
|
||||
@@ -231,6 +245,7 @@ jobs:
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_BUILD_JOBS: 12
|
||||
CARGO_INCREMENTAL: 1
|
||||
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
|
||||
|
||||
@@ -4,5 +4,11 @@
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"@playwright/mcp@latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
79
.wmdev.yaml
79
.wmdev.yaml
@@ -1,79 +0,0 @@
|
||||
services:
|
||||
- name: BE
|
||||
portEnv: BACKEND_PORT
|
||||
- name: FE
|
||||
portEnv: FRONTEND_PORT
|
||||
|
||||
profiles:
|
||||
default:
|
||||
name: default
|
||||
|
||||
sandbox:
|
||||
name: sandbox
|
||||
image: windmill-sandbox
|
||||
envPassthrough:
|
||||
- AWS_ACCESS_KEY_ID
|
||||
- AWS_SECRET_ACCESS_KEY
|
||||
- R2_ENDPOINT
|
||||
- R2_BUCKET
|
||||
- R2_PUBLIC_URL
|
||||
extraMounts:
|
||||
- hostPath: ~/.codex
|
||||
guestPath: /root/.codex
|
||||
writable: true
|
||||
- hostPath: ~/windmill-ee-private
|
||||
writable: true
|
||||
- hostPath: ~/windmill-ee-private__worktrees
|
||||
writable: true
|
||||
systemPrompt: >
|
||||
You are running inside a sandboxed container with full permissions.
|
||||
This worktree is configured with the following ports:
|
||||
|
||||
- Backend: port ${BACKEND_PORT}.
|
||||
Start with: cd backend && PORT=${BACKEND_PORT}
|
||||
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
|
||||
cargo watch -x run
|
||||
|
||||
- Frontend: port ${FRONTEND_PORT}.
|
||||
Start with: cd frontend && REMOTE=http://localhost:${BACKEND_PORT}
|
||||
npm run dev -- --port ${FRONTEND_PORT} --host 0.0.0.0
|
||||
|
||||
--- Screenshots ---
|
||||
You can take screenshots of the frontend UI and upload them to R2
|
||||
for use in PR descriptions.
|
||||
1) Take a screenshot:
|
||||
bunx playwright screenshot --browser chromium
|
||||
http://localhost:${FRONTEND_PORT}/path/to/page /tmp/screenshot.png
|
||||
2) Upload to R2:
|
||||
aws s3 cp /tmp/screenshot.png
|
||||
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/screenshot.png"
|
||||
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||
3) The public URL will be:
|
||||
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
|
||||
4) Include screenshots in PR descriptions as markdown images:
|
||||
/<branch>/screenshot.png)
|
||||
|
||||
--- Terminal Recordings (asciinema) ---
|
||||
You can record terminal sessions and upload them for sharing.
|
||||
asciinema is pre-installed at /usr/local/bin/asciinema.
|
||||
|
||||
1) Write a shell script with the commands to demo. Add sleep
|
||||
delays for readable pacing:
|
||||
- 0.5s after printing a "$ command" line (lets viewer read it)
|
||||
- 1.5-2s after command output (lets viewer absorb the result)
|
||||
- Set GIT_PAGER=cat and PAGER=cat to prevent pager hangs
|
||||
|
||||
2) Record headlessly:
|
||||
asciinema rec --headless --overwrite \
|
||||
-c "bash /tmp/demo.sh" \
|
||||
--window-size 120x50 \
|
||||
--title "Description of demo" \
|
||||
/tmp/demo.cast
|
||||
|
||||
3) Upload to asciinema.org:
|
||||
XDG_DATA_HOME=/tmp/.local/share \
|
||||
asciinema upload --server-url https://asciinema.org /tmp/demo.cast
|
||||
|
||||
linkedRepos:
|
||||
- repo: windmill-labs/windmill-ee-private
|
||||
alias: ee
|
||||
@@ -11,7 +11,7 @@ worktree_prefix: ""
|
||||
window_prefix: "wm-"
|
||||
|
||||
auto_name:
|
||||
model: "gemini-2.5-flash-lite"
|
||||
model: "claude-sonnet-4.6"
|
||||
system_prompt: |
|
||||
Generate a concise git branch name based on the task description.
|
||||
|
||||
@@ -47,7 +47,7 @@ pre_remove:
|
||||
|
||||
panes:
|
||||
- command: >-
|
||||
claude --dangerously-skip-permissions --append-system-prompt
|
||||
claude --append-system-prompt
|
||||
"You are running inside a tmux session with other panes running services.\n
|
||||
Pane layout (current window):\n
|
||||
- Pane 0: this pane (claude agent)\n
|
||||
@@ -57,9 +57,9 @@ panes:
|
||||
When restarting backend or frontend, make sure to use the ports listed in .env.local.\n
|
||||
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check."
|
||||
focus: true
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"'
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x run'
|
||||
split: horizontal
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0'
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm install && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0'
|
||||
split: vertical
|
||||
|
||||
files:
|
||||
@@ -70,3 +70,6 @@ files:
|
||||
sandbox:
|
||||
enabled: false
|
||||
toolchain: off
|
||||
# image, host_commands, and extra_mounts configured in global
|
||||
# ~/.config/workmux/config.yaml — see README_WORKMUX_DEV.md for required
|
||||
# extra_mounts (windmill-ee-private access in sandbox)
|
||||
|
||||
64
CHANGELOG.md
64
CHANGELOG.md
@@ -1,69 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.645.0](https://github.com/windmill-labs/windmill/compare/v1.644.0...v1.645.0) (2026-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add resume and cancel button text options to Slack approval API + formatted args + typo ([#8095](https://github.com/windmill-labs/windmill/issues/8095)) ([c7c828b](https://github.com/windmill-labs/windmill/commit/c7c828b56e7a5f877ef0a78498018ed930bccb23))
|
||||
* Data table as pg resource / trigger ([#8088](https://github.com/windmill-labs/windmill/issues/8088)) ([8e7ba9b](https://github.com/windmill-labs/windmill/commit/8e7ba9b33da2ddba0eba8341219b9a3576a9d95d))
|
||||
* option to preserve on_behalf_of and edited_by for admins and users in the new wm_deployers group ([#8079](https://github.com/windmill-labs/windmill/issues/8079)) ([7ac93f6](https://github.com/windmill-labs/windmill/commit/7ac93f6ee30eb8dfa6ddb9c19697cde93bf7e134))
|
||||
* per-worktree database isolation and Claude Code auto-trust ([09970cd](https://github.com/windmill-labs/windmill/commit/09970cd22b8f19c6d01351f9a9bf4aac170116c2))
|
||||
* show triggers in fork deploy to parent UI. ([#8094](https://github.com/windmill-labs/windmill/issues/8094)) ([935b005](https://github.com/windmill-labs/windmill/commit/935b0058e2b8056e07f8dd8f80ef6de78ca8331f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** fix skip check crash when flow-level skip_expr triggers on first module with skip_if ([#8111](https://github.com/windmill-labs/windmill/issues/8111)) ([7bb450e](https://github.com/windmill-labs/windmill/commit/7bb450edbfccd5c21dc5dbc1e7bf2f2ecc4c779c))
|
||||
* **backend:** pass parent_path for trigger renames in git sync ([#8059](https://github.com/windmill-labs/windmill/issues/8059)) ([5730009](https://github.com/windmill-labs/windmill/commit/5730009404171cbffb67d0296baf9c0aa2858816))
|
||||
* correct asset node x offset inside loops and branches ([#8093](https://github.com/windmill-labs/windmill/issues/8093)) ([1c9ac97](https://github.com/windmill-labs/windmill/commit/1c9ac97f876a82c6ce3b18e30ffdeea79ccd4481))
|
||||
* delete non-session tokens on workspace archive and reject token creation for archived workspaces ([#8082](https://github.com/windmill-labs/windmill/issues/8082)) ([bc67255](https://github.com/windmill-labs/windmill/commit/bc672555a77f3b78ff324a26603d2ab7839df77e))
|
||||
* improve Anthropic API proxy handling and update default models ([#8105](https://github.com/windmill-labs/windmill/issues/8105)) ([a9968d0](https://github.com/windmill-labs/windmill/commit/a9968d0aed446a090b158c3269ffeb6907330933))
|
||||
* optimize slow list_assets query for recents loading ([#8103](https://github.com/windmill-labs/windmill/issues/8103)) ([0c204b6](https://github.com/windmill-labs/windmill/commit/0c204b69bdd319af2706c1add552622678cd343f))
|
||||
* remove duplicate num_columns in test_parse_relation test ([cff9e2c](https://github.com/windmill-labs/windmill/commit/cff9e2c5c22b3c1a0b5891839fe59e4058ded888))
|
||||
* resolve Vite dependency pre-bundling errors ([#8102](https://github.com/windmill-labs/windmill/issues/8102)) ([07ddcd2](https://github.com/windmill-labs/windmill/commit/07ddcd2a08c103246b2b60f9df1ffb477ff97006))
|
||||
* use @-prefixed LIKE pattern for email domain matching ([#8101](https://github.com/windmill-labs/windmill/issues/8101)) ([02d5447](https://github.com/windmill-labs/windmill/commit/02d5447e1d567a18b0d6eb24f3423bd675f6cbe8))
|
||||
* use main runtime handle in QuickJS eval to prevent connection pool poisoning ([#8106](https://github.com/windmill-labs/windmill/issues/8106)) ([af2aca5](https://github.com/windmill-labs/windmill/commit/af2aca56b04c7a3fd25f096f2471292489923431))
|
||||
|
||||
## [1.644.0](https://github.com/windmill-labs/windmill/compare/v1.643.0...v1.644.0) (2026-02-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** detect missing folders on sync push and add 'wmill folder add-missing' ([#8011](https://github.com/windmill-labs/windmill/issues/8011)) ([835db5d](https://github.com/windmill-labs/windmill/commit/835db5d290a151f38f4e879ed7ffbda5d1c4b24f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent concurrent index migrations from re-running on every startup ([#8069](https://github.com/windmill-labs/windmill/issues/8069)) ([8ff2340](https://github.com/windmill-labs/windmill/commit/8ff2340c0c08ce49a809c8958a9862ffb1681642))
|
||||
|
||||
## [1.643.0](https://github.com/windmill-labs/windmill/compare/v1.642.0...v1.643.0) (2026-02-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add fileset resource type support ([32c4b47](https://github.com/windmill-labs/windmill/commit/32c4b474f92f3dbbd2077fab70bdf9e407581626))
|
||||
* add fileset resource type support ([#8063](https://github.com/windmill-labs/windmill/issues/8063)) ([c15b9ab](https://github.com/windmill-labs/windmill/commit/c15b9abe5eb2a1566a7ce4b18784c961d178a669))
|
||||
* add light mode for navigation sidebar ([#8057](https://github.com/windmill-labs/windmill/issues/8057)) ([0935bf9](https://github.com/windmill-labs/windmill/commit/0935bf9fc460c03c6d8469b93036e43714517ef2))
|
||||
* **aiagent:** handle ai agent as tool ([#8031](https://github.com/windmill-labs/windmill/issues/8031)) ([de6fd16](https://github.com/windmill-labs/windmill/commit/de6fd160d56c1037adbbe785f195483c25982e1c))
|
||||
* Unified filters and new runs page ([#8027](https://github.com/windmill-labs/windmill/issues/8027)) ([9b28c85](https://github.com/windmill-labs/windmill/commit/9b28c85469d6b2a8590810b313b030d9f00ee9e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* address code review findings for fileset feature ([1b4489a](https://github.com/windmill-labs/windmill/commit/1b4489acac3b050f0a783548bacfc9bdf33ee593))
|
||||
* address second round of review findings ([753c05a](https://github.com/windmill-labs/windmill/commit/753c05a03089b95b4ade68d3bf61c8818de422ce))
|
||||
* **backend:** decimal between 0 and -1 in mssql ([#8051](https://github.com/windmill-labs/windmill/issues/8051)) ([9686608](https://github.com/windmill-labs/windmill/commit/9686608355615a50c8395f6e2fd51dcc25498226))
|
||||
* **backend:** use filename instead of content_type to detect file fields in multipart form data ([#8054](https://github.com/windmill-labs/windmill/issues/8054)) ([0aa885d](https://github.com/windmill-labs/windmill/commit/0aa885db67d77202205fc1609e841b8ffd9a8121))
|
||||
* exclude app_theme resources from workspace tab ([9c513b2](https://github.com/windmill-labs/windmill/commit/9c513b2c62acc369179fb9e404e1f4007cd854c6))
|
||||
* fileset editor takes full height with matching header ([9ac0789](https://github.com/windmill-labs/windmill/commit/9ac07897cf99f3af27801e435c7376a46ef760c9))
|
||||
* prevent iframe from overriding file selection after file creation ([7f3ddd7](https://github.com/windmill-labs/windmill/commit/7f3ddd7edd3ea993642aadd55cdba0ac2ea1eb9f))
|
||||
* resolve svelte warnings and type error in fileset components ([4c06d74](https://github.com/windmill-labs/windmill/commit/4c06d74bd01ca2dda848be421d70dd5268520992))
|
||||
* restore full-width file tree items in raw app sidebar ([5bac8b0](https://github.com/windmill-labs/windmill/commit/5bac8b093dbe913a563b02573959c64dd405ff61))
|
||||
* suppress iframe setActiveDocument during file population ([1abfeea](https://github.com/windmill-labs/windmill/commit/1abfeea81a645c59934d62257ad869ed7b475634))
|
||||
* update git sync init script to hub version 28158 ([#8061](https://github.com/windmill-labs/windmill/issues/8061)) ([705e186](https://github.com/windmill-labs/windmill/commit/705e186f3d4c7d8f8a88fc84b379ed9fe800a6b2))
|
||||
* use correct column name completed_at instead of ended_at in count_completed_jobs_detail ([#8066](https://github.com/windmill-labs/windmill/issues/8066)) ([3aba0ed](https://github.com/windmill-labs/windmill/commit/3aba0ed2508debdc78a6631e49b074a97635f21d))
|
||||
|
||||
## [1.642.0](https://github.com/windmill-labs/windmill/compare/v1.641.0...v1.642.0) (2026-02-22)
|
||||
|
||||
|
||||
|
||||
@@ -58,10 +58,8 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||||
ln -s /usr/local/lib/cargo/bin/* /usr/local/bin/
|
||||
RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres && \
|
||||
cargo install cargo-watch && \
|
||||
cargo install --locked --git https://github.com/asciinema/asciinema && \
|
||||
ln -sf /usr/local/lib/cargo/bin/sqlx /usr/local/bin/sqlx && \
|
||||
ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch && \
|
||||
ln -sf /usr/local/lib/cargo/bin/asciinema /usr/local/bin/asciinema
|
||||
ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch
|
||||
|
||||
# ── Register dynamic runtime users ───────────────────────────────────────────
|
||||
RUN cat <<'SCRIPT' > /usr/local/bin/register-dynamic-user.sh
|
||||
@@ -175,8 +173,7 @@ RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||
mv /root/.local/share/claude /usr/local/lib/claude && \
|
||||
ln -s "/usr/local/lib/claude/versions/$(basename "$target")" /usr/local/bin/claude && \
|
||||
mkdir -p /tmp/.local/bin && \
|
||||
ln -s /usr/local/bin/claude /tmp/.local/bin/claude && \
|
||||
chmod -R a+rwX /tmp/.local
|
||||
ln -s /usr/local/bin/claude /tmp/.local/bin/claude
|
||||
|
||||
# ── Codex ─────────────────────────────────────────────────────────────────────
|
||||
RUN npm i -g @openai/codex
|
||||
@@ -192,7 +189,6 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/lib/playwright-browsers
|
||||
RUN bun add -g @playwright/test \
|
||||
&& bunx playwright install chromium --with-deps \
|
||||
&& chmod -R a+rwX /usr/local/lib/playwright-browsers \
|
||||
&& chmod -R a+rwX /usr/local/lib/bun/install \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/bunx-*
|
||||
|
||||
# ── AWS CLI (for S3-compatible uploads to R2) ─────────────────────────────────
|
||||
@@ -235,4 +231,4 @@ fi
|
||||
exec "$@"
|
||||
ENTRY
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
@@ -65,7 +65,7 @@ Setting up zsh autocomplete is also recommended — see the [workmux docs](https
|
||||
Each worktree is assigned a **slot** that determines its ports:
|
||||
|
||||
| Slot | Backend | Frontend |
|
||||
| ---- | ------- | -------- |
|
||||
|------|---------|----------|
|
||||
| 0 | 8000 | 3000 |
|
||||
| 1 | 8010 | 3010 |
|
||||
| 2 | 8020 | 3020 |
|
||||
@@ -170,8 +170,7 @@ The setup is defined in `.workmux.yaml` at the repo root. Key sections:
|
||||
- **`post_create`**: Runs `scripts/worktree-env` to generate `.env.local` with port assignments
|
||||
- **`panes`**: Defines the tmux layout (agent, backend, frontend)
|
||||
- **`files.copy`**: Copies `backend/.env` and `scripts/` into each worktree
|
||||
|
||||
The `post_create` hook also copies `frontend/node_modules` using `cp -a` (preserves `.bin/` symlinks that `cp -r` would dereference).
|
||||
- **`files.symlink`**: Symlinks `node_modules` and `.svelte-kit` to avoid reinstalling per worktree
|
||||
|
||||
## Enterprise (EE) Code Access
|
||||
|
||||
@@ -192,98 +191,6 @@ sandbox:
|
||||
|
||||
This mounts both the main EE repo (used by the main worktree) and the EE worktrees directory (used by feature worktrees) into every sandbox container.
|
||||
|
||||
## Cursor SSH Integration (`wmc`)
|
||||
|
||||
`wm-cursor` (aliased as `wmc`) gives each worktree its own Cursor SSH remote window with an independently-focused tmux session. All windows are visible in the status bar across all Cursor terminals, but each one is focused on its own worktree.
|
||||
|
||||
This uses **grouped tmux sessions** — multiple sessions that share the same window list but track focus independently:
|
||||
|
||||
```
|
||||
tmux session: main <-- your main Cursor terminal
|
||||
tmux session: cursor-feat-a <-- Cursor window for feat-a (focused on wm-feat-a)
|
||||
tmux session: cursor-feat-b <-- Cursor window for feat-b (focused on wm-feat-b)
|
||||
\__ all three share the same windows in the status bar
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
Run once from inside tmux on the remote:
|
||||
|
||||
```bash
|
||||
./scripts/wm-cursor setup /home/hugo/projects/windmill
|
||||
```
|
||||
|
||||
This:
|
||||
|
||||
1. **Merges `.vscode/settings.json`** — adds the `wm-tmux` terminal profile (auto-attaches to the `main` tmux session), disables auto port forwarding, configures forwarding for ports 8000/3000/5432, and stops rust-analyzer from auto-starting. Existing settings are preserved.
|
||||
2. **Creates `.vscode/tasks.json`** — auto-starts the dev database (`start-dev-db.sh`) when the folder opens.
|
||||
3. **Adds `wmc` alias to `~/.zshrc`** — so you can use `wmc` from any tmux window.
|
||||
4. **Adds `eval "$(wmc completions)"`** to `~/.zshrc` — provides tab-completion for subcommands and worktree names (for `open`, `open-ee`, and `close`).
|
||||
|
||||
After setup, reopen Cursor's terminal to pick up the new profile.
|
||||
|
||||
### Usage
|
||||
|
||||
All commands run from inside a tmux session (i.e., from Cursor's integrated terminal after setup).
|
||||
|
||||
**Create a new worktree + open Cursor:**
|
||||
|
||||
```bash
|
||||
wmc add -A -p "implement feature X"
|
||||
```
|
||||
|
||||
This runs `workmux add`, creates a grouped tmux session, writes `.vscode/settings.json` in the worktree (with port forwarding matching the worktree's assigned ports), and opens a new Cursor window.
|
||||
|
||||
**Open Cursor for an existing worktree:**
|
||||
|
||||
```bash
|
||||
wmc open my-feature
|
||||
```
|
||||
|
||||
**Open the EE worktree in Cursor (no tmux session):**
|
||||
|
||||
```bash
|
||||
wmc open-ee my-feature
|
||||
```
|
||||
|
||||
This finds the matching `windmill-ee-private__worktrees/<name>` directory and opens it in a new Cursor window.
|
||||
|
||||
**Close a worktree's Cursor window and tmux window (keeps the worktree):**
|
||||
|
||||
```bash
|
||||
wmc close my-feature
|
||||
```
|
||||
|
||||
This kills the grouped tmux session and calls `workmux close` to close the tmux window. The worktree and branch are preserved. Grouped sessions are also automatically cleaned up when you `workmux rm` a worktree (via `scripts/worktree-cleanup`).
|
||||
|
||||
## Cargo Features
|
||||
|
||||
To build the backend with specific Cargo features (e.g., `enterprise`, `parquet`), pass them via `CARGO_FEATURES`. The backend pane reads this from `.env.local` and appends `--features <value>` to the `cargo watch` command.
|
||||
|
||||
**With `wm` (workmux):**
|
||||
|
||||
Set `CARGO_FEATURES` as an environment variable before creating the worktree:
|
||||
|
||||
```bash
|
||||
CARGO_FEATURES="enterprise,parquet" wm add my-feature
|
||||
```
|
||||
|
||||
This gets written to `.env.local` by the `post_create` hook (`scripts/worktree-env`), and the backend pane picks it up automatically.
|
||||
|
||||
**With `wmc` (wm-cursor):**
|
||||
|
||||
Use the `--features` flag:
|
||||
|
||||
```bash
|
||||
# Create a new worktree with features
|
||||
wmc add --features "enterprise,parquet" -A -p "implement feature X"
|
||||
|
||||
# Open an existing worktree with different features
|
||||
wmc open my-feature --features "enterprise,parquet"
|
||||
```
|
||||
|
||||
The `--features` flag exports `CARGO_FEATURES` so the `post_create` hook writes it to `.env.local`. When using `wmc open`, it updates the existing `.env.local` with the new features.
|
||||
|
||||
## Login
|
||||
|
||||
Default credentials: `admin@windmill.dev` / `changeme`
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
"resource",
|
||||
"variable",
|
||||
"ducklake",
|
||||
"datatable",
|
||||
"volume"
|
||||
"datatable"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,6 @@
|
||||
"ordinal": 6,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -57,8 +52,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "03d63d2e64b012f624d2731b5bcb8849c74a9474777be61edf0ed43ddda07ef3"
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow_version (workspace_id, path, value, schema, created_by)\n VALUES ($1, $2, $3, $4::text::json, $5)\n RETURNING id",
|
||||
"query": "INSERT INTO flow_version (workspace_id, path, value, schema, created_by) \n VALUES ($1, $2, $3, $4::text::json, $5)\n RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,5 +22,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a9c805423e700b0acceb7c3dc43d1d3f9d4f56da25f588d281638e449d99a0d9"
|
||||
"hash": "07f5290e90533eac50b890a0d7f4a5e73ac111c838f687fe8647636827aae8b5"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email FROM password WHERE ($2::text = '*' OR email LIKE CONCAT('%@', $2::text)) AND NOT EXISTS (\n SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email\n )",
|
||||
"query": "SELECT email FROM password WHERE ($2::text = '*' OR email LIKE CONCAT('%', $2::text)) AND NOT EXISTS (\n SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email\n )",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -19,5 +19,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "886a921adc115f0a9c6f3a68381bd8f5a16866135120175d9073b9b2c41bbd51"
|
||||
"hash": "0ef37117c369f03236e18f9dbb1f3d52776c8cb73f2507199c6ca16d4d2405ba"
|
||||
}
|
||||
@@ -43,7 +43,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension, is_fileset)\n SELECT $2, name, schema, description, edited_at, created_by, format_extension, is_fileset\n FROM resource_type\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1c2157ce14e90f0751d7f0a9f2dbb3c5a5789a32423e75260098a5300a4af986"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM token WHERE workspace_id = $1 AND label IS DISTINCT FROM 'session' RETURNING token",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2d6607b3c38fe72b5663c32de58dacbabed4c5ae28101e3ae2694f96fd055a91"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n SELECT $1::text, email, false, $3 FROM password WHERE ($2::text = '*' OR email LIKE CONCAT('%@', $2::text)) AND NOT EXISTS (\n SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email\n )\n ON CONFLICT DO NOTHING",
|
||||
"query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n SELECT $1::text, email, false, $3 FROM password WHERE ($2::text = '*' OR email LIKE CONCAT('%', $2::text)) AND NOT EXISTS (\n SELECT 1 FROM usr WHERE workspace_id = $1::text AND email = password.email\n )\n ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c0fad64e5d707ffa29d236f558e23b608168dc3a1b3857d2ad33ec20627acbff"
|
||||
"hash": "2e1d1c59bfc53d58962251822c85cf9a26e3b2888702e5e9d5fc1b082901df09"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = COALESCE($24, email),\n edited_by = $25\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -183,8 +183,6 @@
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
@@ -222,5 +220,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0"
|
||||
"hash": "4144c87c25a939aafb2f57da189d94d038bcad7a36fbf87e0403c89a979c5b3f"
|
||||
}
|
||||
@@ -42,7 +42,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
16
backend/.sqlx/query-61b37cb4db6e60c2d35f7d23db5afbe04e040a8dcd1d93afaaaa320665c8779a.json
generated
Normal file
16
backend/.sqlx/query-61b37cb4db6e60c2d35f7d23db5afbe04e040a8dcd1d93afaaaa320665c8779a.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH prev_sd AS (\n DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock\n ) INSERT INTO debounce_stale_data (job_id, to_relock)\n VALUES ($2, array_cat((SELECT to_relock FROM prev_sd), $3))\n ON CONFLICT (job_id) DO UPDATE SET to_relock = EXCLUDED.to_relock\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "61b37cb4db6e60c2d35f7d23db5afbe04e040a8dcd1d93afaaaa320665c8779a"
|
||||
}
|
||||
15
backend/.sqlx/query-7abd579d3ec97853ac36cc8dad29013eb133a28cd848bf8fdf9571b2ee402a3e.json
generated
Normal file
15
backend/.sqlx/query-7abd579d3ec97853ac36cc8dad29013eb133a28cd848bf8fdf9571b2ee402a3e.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension)\n SELECT $2, name, schema, description, edited_at, created_by, format_extension\n FROM resource_type\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7abd579d3ec97853ac36cc8dad29013eb133a28cd848bf8fdf9571b2ee402a3e"
|
||||
}
|
||||
@@ -37,11 +37,6 @@
|
||||
"ordinal": 6,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -56,8 +51,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234"
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT schema, description, format_extension, is_fileset\n FROM resource_type\n WHERE workspace_id = $1 AND name = $2",
|
||||
"query": "SELECT schema, description, format_extension\n FROM resource_type\n WHERE workspace_id = $1 AND name = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -17,11 +17,6 @@
|
||||
"ordinal": 2,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -33,9 +28,8 @@
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2768622b76ad92c05f4f44d997aff285707e1a43ce85e5bb8e87849d78a0637f"
|
||||
"hash": "7bc9fc05dbd162866bef1fdd3e7faeb50429881ed1bc962903f06e4b3d5f8d44"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO token (token, email, label, expiration, scopes, workspace_id)\n SELECT $1::varchar, $2::varchar, $3::varchar, now() + ($4 || ' seconds')::interval, $5::text[], $6::varchar\n WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $6 AND deleted = true)",
|
||||
"query": "INSERT INTO token (token, email, label, expiration, scopes, workspace_id)\n VALUES ($1, $2, $3, now() + ($4 || ' seconds')::interval, $5, $6)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d32448f6b329cf98dad42b218a630c0cf40a99edb4ae9fe3e9be485ab1077b3a"
|
||||
"hash": "7e4aa6b19b110bca423b3a3f428826d92b9808c64ef989fef2142bc8e02d6630"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT runnable_id as \"runnable_id: ScriptHash\", raw_flow as \"raw_flow: _\", kind as \"kind: _\", parent_job, flow_step_id FROM v2_job WHERE id = $1",
|
||||
"query": "SELECT runnable_id as \"runnable_id: ScriptHash\", raw_flow as \"raw_flow: _\", kind as \"kind: _\" FROM v2_job WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -42,21 +42,12 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "parent_job",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "flow_step_id",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -67,10 +58,8 @@
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7aaa5b0bd873c2029e2201d287ea0aaae04678ac105374bbe387e534a6cb6333"
|
||||
"hash": "805d633de90fee335f1726284eda0dbc200d45960fb8dea867492c8c7dd096d5"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT name, format_extension, is_fileset FROM resource_type WHERE (format_extension IS NOT NULL OR is_fileset = true) AND (workspace_id = $1 OR workspace_id = 'admins')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "842775bcf91d747abb11ffe9c98fa1208595e012590606ef6667ea3a78105883"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT on_behalf_of_email FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "85a6a85fd126a8bfedd65d6b38d22c65911ab9cf0414c33a3321a1d43af49795"
|
||||
}
|
||||
@@ -102,7 +102,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
"resource",
|
||||
"variable",
|
||||
"ducklake",
|
||||
"datatable",
|
||||
"volume"
|
||||
"datatable"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT policy FROM app WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "policy",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b12fba75788e44daefd9b3540a3aebe9167431aaa0a902b4558bc141c85ed825"
|
||||
}
|
||||
@@ -102,7 +102,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,6 @@
|
||||
"ordinal": 6,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -56,8 +51,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b8d392ccfcccafe0c19511b3567bc11779b1052b0948c410468a8aeba1d26d33"
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO token\n (token, email, label, expiration, super_admin, scopes, workspace_id)\n SELECT $1, $2, $3, $4, $5, $6, $7\n WHERE $7::varchar IS NULL OR NOT EXISTS(\n SELECT 1 FROM workspace WHERE id = $7 AND deleted = true\n )",
|
||||
"query": "INSERT INTO token\n (token, email, label, expiration, super_admin, scopes, workspace_id)\n VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -16,5 +16,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e33be0991702ae3a295db7defc6d19d914307a95d72bb0fb447e5b367d52f6a0"
|
||||
"hash": "c624f15f3e321b1eecf123da9bf0b18e8c1d16ef25ffb9d04e5447d0d583d55c"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821",
|
||||
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821 OR\n version=20260207000001 OR version=20260207000002 OR version=20260207000003 OR version=20260207000004",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d"
|
||||
"hash": "c6bcf0d9e211bc03e3338682295f4995e1d622917367c478742addd073245ad5"
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2",
|
||||
"query": "\n SELECT name, format_extension FROM resource_type WHERE format_extension IS NOT NULL AND (workspace_id = $1 OR workspace_id = 'admins')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "edited_by",
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854"
|
||||
"hash": "cf1cef7e0fe2e7e3db96b0ec005360361b9eec023a6fc2a4a7a917f59d86af4d"
|
||||
}
|
||||
@@ -41,7 +41,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO group_\n VALUES ($1, 'wm_deployers', 'Members can preserve the original author when deploying to this workspace')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dda45bcc53e94659838e98b6b9e7a55be0e31aee3008d5190f09c1f15e5b47dd"
|
||||
}
|
||||
@@ -31,7 +31,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT on_behalf_of_email FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e1f43cb65201b4f0965a4e18f0c918ae51fee667472d0cc2796ffdba4138d2ee"
|
||||
}
|
||||
@@ -37,7 +37,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT on_behalf_of_email FROM script WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "on_behalf_of_email",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e8d948274840699c5f7485ee4bc00b72c11bd226f99eade7e9a0da4605539283"
|
||||
}
|
||||
@@ -37,11 +37,6 @@
|
||||
"ordinal": 6,
|
||||
"name": "format_extension",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_fileset",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -54,8 +49,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "eb1f7f01461f5a7540c273b37e5d578c31cf151ab3ef813f7aada76533761e12"
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"aiagent",
|
||||
"unassigned_script",
|
||||
"unassigned_flow",
|
||||
"unassigned_singlestepflow"
|
||||
"unassigned_singlestepflow",
|
||||
"snapshotbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, format_extension, is_fileset, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
|
||||
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, format_extension, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now())",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -10,11 +10,10 @@
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool"
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5899c7614f195fdd23e38389e52b004f957aafa2201b80638b5f87a625373f00"
|
||||
"hash": "ffedbb3a2676a6d7b71f81f89109a02a8dba90d40144e942527f8a3fc36dfbc1"
|
||||
}
|
||||
204
backend/Cargo.lock
generated
204
backend/Cargo.lock
generated
@@ -490,7 +490,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"num",
|
||||
"regex",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2259,9 +2259,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@@ -3507,7 +3507,7 @@ dependencies = [
|
||||
"log",
|
||||
"recursive",
|
||||
"regex",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5526,7 +5526,7 @@ checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5537,7 +5537,7 @@ checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5588,7 +5588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix 1.1.4",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -5804,7 +5804,7 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -6254,7 +6254,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8055,7 +8055,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
"redox_syscall 0.7.2",
|
||||
"redox_syscall 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8092,9 +8092,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.24"
|
||||
version = "1.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839"
|
||||
checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -8116,9 +8116,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
@@ -9729,9 +9729,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.3.0"
|
||||
version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
|
||||
|
||||
[[package]]
|
||||
name = "p224"
|
||||
@@ -10989,9 +10989,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.7.2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4"
|
||||
checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
@@ -11047,7 +11047,7 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11058,7 +11058,7 @@ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11075,9 +11075,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
@@ -11598,14 +11598,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -13774,7 +13774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
@@ -13838,14 +13838,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.26.0"
|
||||
version = "3.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.1",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -13864,7 +13864,7 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
@@ -14708,7 +14708,7 @@ checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"regex-syntax 0.8.10",
|
||||
"regex-syntax 0.8.9",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
@@ -15725,7 +15725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15789,7 +15789,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15802,7 +15802,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15940,7 +15940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15963,7 +15963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15976,7 +15976,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16002,7 +16002,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16012,7 +16012,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16029,7 +16029,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
@@ -16052,7 +16052,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16075,7 +16075,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16091,7 +16091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16111,7 +16111,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16131,7 +16131,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16145,7 +16145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16171,7 +16171,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16196,7 +16196,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"flate2",
|
||||
@@ -16213,7 +16213,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16234,7 +16234,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16254,7 +16254,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16284,7 +16284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16311,7 +16311,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16323,7 +16323,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.7.9",
|
||||
@@ -16346,7 +16346,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16360,7 +16360,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16390,7 +16390,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16404,7 +16404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16423,7 +16423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -16522,7 +16522,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16541,7 +16541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16556,7 +16556,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16580,7 +16580,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16597,7 +16597,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16613,7 +16613,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16634,7 +16634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16665,7 +16665,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-oauth2",
|
||||
@@ -16689,7 +16689,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -16723,7 +16723,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16741,7 +16741,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -16750,7 +16750,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16762,7 +16762,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16774,7 +16774,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -16786,7 +16786,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16798,7 +16798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16810,7 +16810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -16821,7 +16821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16832,7 +16832,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16845,7 +16845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16869,7 +16869,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16883,7 +16883,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -16900,7 +16900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16915,7 +16915,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16934,7 +16934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -16945,7 +16945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16982,7 +16982,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17020,7 +17020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -17030,7 +17030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17059,7 +17059,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -17082,7 +17082,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17115,7 +17115,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17135,7 +17135,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17169,7 +17169,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17204,7 +17204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17227,7 +17227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17251,7 +17251,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17275,7 +17275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17310,7 +17310,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17338,7 +17338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17361,7 +17361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17379,7 +17379,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -18252,7 +18252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix 1.1.4",
|
||||
"rustix 1.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -76,7 +76,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.645.0"
|
||||
version = "1.642.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2fb7884849a563bae023574baa2d55fa1fab1176
|
||||
0fede4b1086bc1456be9cc55b203228c979c5c5e
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE resource_type DROP COLUMN is_fileset;
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE resource_type ADD COLUMN is_fileset BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -1,5 +0,0 @@
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
SELECT id, 'wm_deployers', 'Members can preserve the original author when deploying to this workspace', '{}'::jsonb
|
||||
FROM workspace
|
||||
WHERE NOT deleted
|
||||
ON CONFLICT (workspace_id, name) DO UPDATE SET summary = EXCLUDED.summary;
|
||||
@@ -1,10 +0,0 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'custom_instance_user') THEN
|
||||
ALTER ROLE custom_instance_user NOREPLICATION;
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
RAISE NOTICE 'Error revoking REPLICATION from custom_instance_user: %', SQLERRM;
|
||||
END
|
||||
$$;
|
||||
@@ -1,10 +0,0 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'custom_instance_user') THEN
|
||||
ALTER ROLE custom_instance_user REPLICATION;
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
RAISE NOTICE 'Error granting REPLICATION to custom_instance_user: %', SQLERRM;
|
||||
END
|
||||
$$;
|
||||
@@ -1,5 +0,0 @@
|
||||
DROP INDEX IF EXISTS idx_asset_ws_path_kind_recent;
|
||||
|
||||
-- Restore the dropped indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_asset_workspace_created_id ON asset (workspace_id, created_at DESC, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_asset_kind_path ON asset (workspace_id, kind, path);
|
||||
@@ -1,11 +0,0 @@
|
||||
-- Covering index for the list_assets CTE: GROUP BY (path, kind) + MAX(created_at, id) + ORDER BY
|
||||
-- Includes usage_kind and usage_path to allow full index-only scan (avoiding heap lookups for filter conditions)
|
||||
CREATE INDEX IF NOT EXISTS idx_asset_ws_path_kind_recent
|
||||
ON asset (workspace_id, path, kind, created_at DESC, id DESC)
|
||||
INCLUDE (usage_kind, usage_path);
|
||||
|
||||
-- Drop indexes now subsumed by idx_asset_ws_path_kind_recent:
|
||||
-- idx_asset_workspace_created_id (workspace_id, created_at DESC, id DESC) - only used by list_assets CTE
|
||||
-- idx_asset_kind_path (workspace_id, kind, path) - only used by list_assets CTE/outer join, covered by new index + PK
|
||||
DROP INDEX IF EXISTS idx_asset_workspace_created_id;
|
||||
DROP INDEX IF EXISTS idx_asset_kind_path;
|
||||
@@ -137,7 +137,7 @@ raw_app: path(char), version(int), workspace_id(char), summary(char), edited_at(
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resource: workspace_id(char), path(char), value(jsonb), description(text), resource_type(char), extra_perms(jsonb), edited_at(ts), created_by(char)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resource_type: workspace_id(char), name(char), schema(jsonb), description(text), edited_at(ts), created_by(char), format_extension(char), is_fileset(bool)
|
||||
resource_type: workspace_id(char), name(char), schema(jsonb), description(text), edited_at(ts), created_by(char), format_extension(char)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
resume_job: id(uuid), job(uuid), flow(uuid), created_at(ts), value(jsonb), approver(char), resume_id(int), approved(bool)
|
||||
FK: (flow) -> v2_job_queue(id)
|
||||
|
||||
@@ -447,7 +447,6 @@ def main():
|
||||
deployment_message: None,
|
||||
visible_to_runner_only: None,
|
||||
on_behalf_of_email: None,
|
||||
preserve_on_behalf_of: None,
|
||||
ws_error_handler_muted: None,
|
||||
})
|
||||
.send()
|
||||
@@ -509,7 +508,6 @@ def main():
|
||||
policy: None,
|
||||
deployment_message: None,
|
||||
custom_path: None,
|
||||
preserve_on_behalf_of: None,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
|
||||
186
backend/tests/fixtures/preserve_on_behalf_of.sql
vendored
186
backend/tests/fixtures/preserve_on_behalf_of.sql
vendored
@@ -1,186 +0,0 @@
|
||||
-- Fixture for preserve_on_behalf_of integration tests
|
||||
-- Extends base.sql with a deployer user in the wm_deployers group
|
||||
|
||||
-- Include all base setup (workspace, admin user, etc.)
|
||||
INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ('test-workspace', 'test-workspace', 'test-user')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key) VALUES
|
||||
('test-workspace', 'cloud', 'test-key')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id) VALUES
|
||||
('test-workspace')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES
|
||||
('test-workspace', 'all', 'All users', '{}')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Create the wm_deployers group
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES
|
||||
('test-workspace', 'wm_deployers', 'Users allowed to deploy and preserve on_behalf_of', '{}')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username)
|
||||
VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Deployer user (non-admin but in wm_deployers group)
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('deployer@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Deployer User')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Original user whose on_behalf_of should be preserved
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('original@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Original User')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Deployer user in workspace
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'deployer@windmill.dev', 'deployer-user', false, 'User')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Original user in workspace (whose on_behalf_of should be preserved)
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'original@windmill.dev', 'original-user', false, 'User')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Add deployer user to wm_deployers group
|
||||
INSERT INTO usr_to_group(workspace_id, group_, usr) VALUES
|
||||
('test-workspace', 'wm_deployers', 'deployer-user')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Tokens for all users
|
||||
INSERT INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO token(token, email, label, super_admin) VALUES ('DEPLOYER_TOKEN', 'deployer@windmill.dev', 'deployer token', false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
INSERT INTO token(token, email, label, super_admin) VALUES ('ORIGINAL_TOKEN', 'original@windmill.dev', 'original token', false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "notify_insert_on_completed_job" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('completed', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
DROP TRIGGER IF EXISTS "notify_insert_on_completed_job" ON "v2_job_completed";
|
||||
CREATE TRIGGER "notify_insert_on_completed_job"
|
||||
AFTER INSERT ON "v2_job_completed"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_insert_on_completed_job" ();
|
||||
|
||||
CREATE OR REPLACE FUNCTION "notify_queue" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('queued', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
DROP TRIGGER IF EXISTS "notify_queue_after_insert" ON "v2_job_queue";
|
||||
CREATE TRIGGER "notify_queue_after_insert"
|
||||
AFTER INSERT ON "v2_job_queue"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
DROP TRIGGER IF EXISTS "notify_queue_after_flow_status_update" ON "v2_job_status";
|
||||
CREATE TRIGGER "notify_queue_after_flow_status_update"
|
||||
AFTER UPDATE ON "v2_job_status"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status)
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
-- Apply phase 4:
|
||||
DROP FUNCTION IF EXISTS v2_job_after_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE;
|
||||
|
||||
DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE;
|
||||
|
||||
ALTER TABLE v2_job_queue
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __last_ping CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_status CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __same_worker CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __pre_run_error CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __mem_peak CASCADE,
|
||||
DROP COLUMN IF EXISTS __root_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __leaf_jobs CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrent_limit CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE,
|
||||
DROP COLUMN IF EXISTS __timeout CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_step_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __cache_ttl CASCADE;
|
||||
|
||||
LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE;
|
||||
ALTER TABLE v2_job_completed
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_at CASCADE,
|
||||
DROP COLUMN IF EXISTS __success CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_skipped CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __tag CASCADE,
|
||||
DROP COLUMN IF EXISTS __priority CASCADE;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,306 +0,0 @@
|
||||
//! Integration tests for workspace protection rulesets.
|
||||
//!
|
||||
//! Tests verify that DisableDirectDeployment protection rules correctly
|
||||
//! block/allow operations based on user permissions.
|
||||
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::workspaces::invalidate_protection_rules_cache;
|
||||
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", format!("Bearer {}", token))
|
||||
}
|
||||
|
||||
fn new_script(path: &str, summary: &str) -> serde_json::Value {
|
||||
json!({
|
||||
"path": path,
|
||||
"summary": summary,
|
||||
"description": "",
|
||||
"content": "export async function main() { return 42; }",
|
||||
"language": "deno",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn new_flow(path: &str, summary: &str) -> serde_json::Value {
|
||||
json!({
|
||||
"path": path,
|
||||
"summary": summary,
|
||||
"description": "",
|
||||
"value": { "modules": [] },
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Comprehensive test for protection rules functionality.
|
||||
/// Tests all essential cases in a single test to avoid cache interference.
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_protection_rules(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
invalidate_protection_rules_cache("test-workspace");
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let base = format!("http://localhost:{port}/api/w/test-workspace");
|
||||
|
||||
// ========================================
|
||||
// 1. Without protection rule, non-admin can create scripts and flows
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&new_script("u/test-user-2/script_no_rule", "No rule"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"Should create script without rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/flows/create")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&new_flow("u/test-user-2/flow_no_rule", "No rule"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"Should create flow without rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 2. Non-admin cannot create protection rules
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/workspaces/protection_rules")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&json!({
|
||||
"name": "unauthorized-rule",
|
||||
"rules": ["DisableDirectDeployment"],
|
||||
"bypass_users": [],
|
||||
"bypass_groups": []
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"Non-admin should not create rules: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 3. Admin creates protection rule
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/workspaces/protection_rules")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&json!({
|
||||
"name": "test-rule",
|
||||
"rules": ["DisableDirectDeployment"],
|
||||
"bypass_users": [],
|
||||
"bypass_groups": []
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
200,
|
||||
"Admin should create rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 4. With rule, non-admin is blocked from creating scripts/flows
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&new_script("u/test-user-2/blocked_script", "Blocked"))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"Non-admin should be blocked from scripts: {}",
|
||||
resp.status()
|
||||
);
|
||||
let body = resp.text().await?;
|
||||
assert!(
|
||||
body.contains("blocked") || body.contains("Blocked"),
|
||||
"Error should mention blocking: {}",
|
||||
body
|
||||
);
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/flows/create")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&new_flow("u/test-user-2/blocked_flow", "Blocked"))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"Non-admin should be blocked from flows: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 5. Admin bypasses protection rule
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&new_script("u/test-user/admin_script", "Admin"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"Admin should bypass rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 6. Update rule to bypass test-user-2
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/workspaces/protection_rules/test-rule")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&json!({
|
||||
"rules": ["DisableDirectDeployment"],
|
||||
"bypass_users": ["test-user-2"],
|
||||
"bypass_groups": []
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
200,
|
||||
"Should update rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// Invalidate cache to pick up the update
|
||||
invalidate_protection_rules_cache("test-workspace");
|
||||
|
||||
// ========================================
|
||||
// 7. Bypassed user can now create
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN_2",
|
||||
)
|
||||
.json(&new_script("u/test-user-2/bypassed_script", "Bypassed"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"Bypassed user should create: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 8. Non-bypassed user (test-user-3) is still blocked
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN_3",
|
||||
)
|
||||
.json(&new_script("u/test-user-3/still_blocked", "Blocked"))
|
||||
.send()
|
||||
.await?;
|
||||
assert!(
|
||||
!resp.status().is_success(),
|
||||
"Non-bypassed user should be blocked: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 9. Delete rule
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().delete(format!("{base}/workspaces/protection_rules/test-rule")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
200,
|
||||
"Should delete rule: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// Invalidate cache to pick up the deletion
|
||||
invalidate_protection_rules_cache("test-workspace");
|
||||
|
||||
// ========================================
|
||||
// 10. After deletion, non-admin can create again
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{base}/scripts/create")),
|
||||
"SECRET_TOKEN_3",
|
||||
)
|
||||
.json(&new_script("u/test-user-3/after_delete", "After delete"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"Should create after rule deletion: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// 11. Verify rule list is empty
|
||||
// ========================================
|
||||
|
||||
let resp = authed(
|
||||
client().get(format!("{base}/workspaces/protection_rules")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let rules: Vec<serde_json::Value> = resp.json().await?;
|
||||
assert!(rules.is_empty(), "Should have no rules after deletion");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -27,13 +27,9 @@ struct ListAssetsQuery {
|
||||
per_page: i64,
|
||||
cursor_created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
cursor_id: Option<i64>,
|
||||
pub asset_path: Option<String>,
|
||||
pub usage_path: Option<String>,
|
||||
pub asset_kinds: Option<String>,
|
||||
// Exact path match filter
|
||||
pub path: Option<String>,
|
||||
// Filter by matching a subset of the columns using base64 encoded json subset
|
||||
pub columns: Option<String>,
|
||||
asset_path: Option<String>,
|
||||
usage_path: Option<String>,
|
||||
asset_kinds: Option<String>,
|
||||
}
|
||||
|
||||
fn default_per_page() -> i64 {
|
||||
@@ -79,24 +75,12 @@ async fn list_assets(
|
||||
|
||||
let mut param_count = 2; // $1 = workspace_id, $2 = limit
|
||||
|
||||
// Asset path filter (ILIKE pattern match)
|
||||
// Asset path filter
|
||||
if query.asset_path.is_some() {
|
||||
param_count += 1;
|
||||
asset_summary_filters.push(format!("asset.path ILIKE ${}", param_count));
|
||||
}
|
||||
|
||||
// Exact path filter
|
||||
if query.path.is_some() {
|
||||
param_count += 1;
|
||||
asset_summary_filters.push(format!("asset.path = ${}", param_count));
|
||||
}
|
||||
|
||||
// Columns filter (check if JSONB has all specified keys)
|
||||
if query.columns.is_some() {
|
||||
param_count += 1;
|
||||
asset_summary_filters.push(format!("asset.columns ?& ${}", param_count));
|
||||
}
|
||||
|
||||
// Usage path filter - for jobs, also check runnable_path
|
||||
let needs_job_join_in_cte = query.usage_path.is_some();
|
||||
if query.usage_path.is_some() {
|
||||
@@ -144,7 +128,7 @@ async fn list_assets(
|
||||
format!(
|
||||
r#"FROM asset
|
||||
LEFT JOIN v2_job job_cte ON asset.usage_kind = 'job'
|
||||
AND job_cte.id = CASE WHEN asset.usage_kind = 'job' THEN asset.usage_path::uuid END
|
||||
AND asset.usage_path = job_cte.id::text
|
||||
AND job_cte.workspace_id = $1"#
|
||||
)
|
||||
} else {
|
||||
@@ -209,7 +193,7 @@ async fn list_assets(
|
||||
) = resource.path
|
||||
AND resource.workspace_id = $1
|
||||
LEFT JOIN v2_job job ON asset.usage_kind = 'job'
|
||||
AND job.id = CASE WHEN asset.usage_kind = 'job' THEN asset.usage_path::uuid END
|
||||
AND asset.usage_path = job.id::text
|
||||
AND job.workspace_id = $1
|
||||
WHERE asset.workspace_id = $1
|
||||
AND (asset.kind <> 'resource' OR resource.path IS NOT NULL)
|
||||
@@ -227,20 +211,6 @@ async fn list_assets(
|
||||
query_builder = query_builder.bind(format!("%{}%", asset_path));
|
||||
}
|
||||
|
||||
if let Some(ref path) = query.path {
|
||||
query_builder = query_builder.bind(path);
|
||||
}
|
||||
|
||||
if let Some(ref columns) = query.columns {
|
||||
// Columns is a comma-separated string, split into array for ?& operator
|
||||
let columns_array: Vec<String> = columns
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
query_builder = query_builder.bind(columns_array);
|
||||
}
|
||||
|
||||
if let Some(ref usage_path) = query.usage_path {
|
||||
query_builder = query_builder.bind(format!("%{}%", usage_path));
|
||||
}
|
||||
|
||||
@@ -534,13 +534,10 @@ pub async fn create_token_internal(
|
||||
));
|
||||
}
|
||||
}
|
||||
let rows = sqlx::query!(
|
||||
sqlx::query!(
|
||||
"INSERT INTO token
|
||||
(token, email, label, expiration, super_admin, scopes, workspace_id)
|
||||
SELECT $1, $2, $3, $4, $5, $6, $7
|
||||
WHERE $7::varchar IS NULL OR NOT EXISTS(
|
||||
SELECT 1 FROM workspace WHERE id = $7 AND deleted = true
|
||||
)",
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
token,
|
||||
authed.email,
|
||||
token_config.label,
|
||||
@@ -551,11 +548,6 @@ pub async fn create_token_internal(
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
if rows.rows_affected() == 0 {
|
||||
return Err(Error::BadRequest(
|
||||
"Cannot create a token for an archived workspace".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
|
||||
@@ -503,11 +503,7 @@ async fn create_flow(
|
||||
nf.tag,
|
||||
nf.dedicated_worker,
|
||||
nf.visible_to_runner_only.unwrap_or(false),
|
||||
windmill_common::resolve_on_behalf_of_email(
|
||||
nf.on_behalf_of_email.as_deref(),
|
||||
nf.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
),
|
||||
nf.on_behalf_of_email.and(Some(&authed.email)),
|
||||
nf.ws_error_handler_muted.unwrap_or(false),
|
||||
sqlx::types::Json(&nf.value) as _,
|
||||
schema_str,
|
||||
@@ -517,7 +513,7 @@ async fn create_flow(
|
||||
.await?;
|
||||
|
||||
let version = sqlx::query_scalar!(
|
||||
"INSERT INTO flow_version (workspace_id, path, value, schema, created_by)
|
||||
"INSERT INTO flow_version (workspace_id, path, value, schema, created_by)
|
||||
VALUES ($1, $2, $3, $4::text::json, $5)
|
||||
RETURNING id",
|
||||
w_id,
|
||||
@@ -559,29 +555,6 @@ async fn create_flow(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
nf.on_behalf_of_email.as_deref(),
|
||||
nf.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.email,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"flows.on_behalf_of",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(&nf.path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "create"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
|
||||
if let Some(dm) = nf.deployment_message {
|
||||
@@ -967,11 +940,7 @@ async fn update_flow(
|
||||
nf.tag,
|
||||
nf.dedicated_worker,
|
||||
nf.visible_to_runner_only.unwrap_or(false),
|
||||
windmill_common::resolve_on_behalf_of_email(
|
||||
nf.on_behalf_of_email.as_deref(),
|
||||
nf.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
),
|
||||
nf.on_behalf_of_email.and(Some(&authed.email)),
|
||||
nf.ws_error_handler_muted.unwrap_or(false),
|
||||
sqlx::types::Json(&nf.value) as _,
|
||||
schema_str,
|
||||
@@ -1134,29 +1103,6 @@ async fn update_flow(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
nf.on_behalf_of_email.as_deref(),
|
||||
nf.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.email,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"flows.on_behalf_of",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&nf.path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "update"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
|
||||
@@ -54,21 +54,6 @@ INSERT INTO resource (workspace_id, path, value, description, resource_type, ext
|
||||
VALUES ('test-workspace', 'u/test-user/scalar_var_resource', '"$var:u/test-user/db_password"',
|
||||
'Scalar var ref', 'string', '{}', 'test-user');
|
||||
|
||||
-- === fileset resource type test data ===
|
||||
|
||||
INSERT INTO resource_type (workspace_id, name, schema, description, created_by, is_fileset)
|
||||
VALUES ('test-workspace', 'test_fileset', '{}',
|
||||
'Test fileset type', 'test-user', true);
|
||||
|
||||
INSERT INTO resource_type (workspace_id, name, schema, description, created_by, format_extension)
|
||||
VALUES ('test-workspace', 'test_file', '{"type": "object", "properties": {"content": {"type": "string"}}}',
|
||||
'Test file type', 'test-user', 'txt');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/fileset_resource',
|
||||
'{"config.yaml": "key: value", "data/input.json": "{\"items\": []}"}',
|
||||
'A fileset resource', 'test_fileset', '{}', 'test-user');
|
||||
|
||||
-- === mcp_tools test data ===
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
|
||||
@@ -69,12 +69,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
assert_eq!(resp.status(), 404);
|
||||
|
||||
// --- get_value_interpolated ---
|
||||
let resp = authed_get(
|
||||
port,
|
||||
"get_value_interpolated",
|
||||
"u/test-user/simple_resource",
|
||||
)
|
||||
.await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/simple_resource").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -82,12 +78,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// $var: interpolation
|
||||
let resp = authed_get(
|
||||
port,
|
||||
"get_value_interpolated",
|
||||
"u/test-user/resource_with_var",
|
||||
)
|
||||
.await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/resource_with_var").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -95,12 +87,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// $res: interpolation
|
||||
let resp = authed_get(
|
||||
port,
|
||||
"get_value_interpolated",
|
||||
"u/test-user/resource_with_res",
|
||||
)
|
||||
.await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/resource_with_res").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -108,7 +96,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// mixed $var: and $res: refs
|
||||
let resp = authed_get(port, "get_value_interpolated", "u/test-user/resource_mixed").await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/resource_mixed").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -116,12 +105,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// chained $res: -> $var:
|
||||
let resp = authed_get(
|
||||
port,
|
||||
"get_value_interpolated",
|
||||
"u/test-user/chained_resource",
|
||||
)
|
||||
.await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/chained_resource").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -129,7 +114,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// null value
|
||||
let resp = authed_get(port, "get_value_interpolated", "u/test-user/null_resource").await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/null_resource").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(
|
||||
resp.json::<serde_json::Value>().await?,
|
||||
@@ -137,7 +123,8 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// not found
|
||||
let resp = authed_get(port, "get_value_interpolated", "u/test-user/nonexistent").await;
|
||||
let resp =
|
||||
authed_get(port, "get_value_interpolated", "u/test-user/nonexistent").await;
|
||||
assert_eq!(resp.status(), 404);
|
||||
|
||||
// array passthrough
|
||||
@@ -175,9 +162,7 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
"expected at least 10 resources from fixture, got {}",
|
||||
list.len()
|
||||
);
|
||||
assert!(list
|
||||
.iter()
|
||||
.any(|r| r["path"] == "u/test-user/simple_resource"));
|
||||
assert!(list.iter().any(|r| r["path"] == "u/test-user/simple_resource"));
|
||||
|
||||
// list with resource_type filter
|
||||
let resp = authed(client().get(format!("{base}/list?resource_type=mcp_server")))
|
||||
@@ -274,11 +259,9 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
assert_eq!(body["description"], "Updated description");
|
||||
|
||||
// --- update_value ---
|
||||
let resp = authed(client().post(resource_url(
|
||||
port,
|
||||
"update_value",
|
||||
"u/test-user/new_resource",
|
||||
)))
|
||||
let resp = authed(
|
||||
client().post(resource_url(port, "update_value", "u/test-user/new_resource")),
|
||||
)
|
||||
.json(&json!({"value": {"url": "https://final.com"}}))
|
||||
.send()
|
||||
.await
|
||||
@@ -292,44 +275,35 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
// --- delete ---
|
||||
let resp = authed(client().delete(resource_url(port, "delete", "u/test-user/new_resource")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = authed(
|
||||
client().delete(resource_url(port, "delete", "u/test-user/new_resource")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
let resp = authed_get(port, "exists", "u/test-user/new_resource").await;
|
||||
assert_eq!(resp.json::<bool>().await?, false);
|
||||
|
||||
// delete nonexistent -> 404
|
||||
let resp = authed(client().delete(resource_url(port, "delete", "u/test-user/new_resource")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = authed(
|
||||
client().delete(resource_url(port, "delete", "u/test-user/new_resource")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 404);
|
||||
|
||||
// --- file_resource_type_to_file_ext_map ---
|
||||
let resp = authed(client().get(format!("{base}/file_resource_type_to_file_ext_map")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = authed(client().get(format!(
|
||||
"{base}/file_resource_type_to_file_ext_map"
|
||||
)))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let ext_map = resp.json::<serde_json::Value>().await?;
|
||||
// Verify the map includes fileset type info with is_fileset flag (no format_extension)
|
||||
let fileset_info = &ext_map["test_fileset"];
|
||||
assert_eq!(fileset_info["format_extension"], serde_json::Value::Null);
|
||||
assert_eq!(fileset_info["is_fileset"], true);
|
||||
// Verify non-fileset file type
|
||||
let file_info = &ext_map["test_file"];
|
||||
assert_eq!(file_info["format_extension"], "txt");
|
||||
assert_eq!(file_info["is_fileset"], false);
|
||||
|
||||
// --- fileset resource value ---
|
||||
let resp = authed_get(port, "get_value", "u/test-user/fileset_resource").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let fileset_val = resp.json::<serde_json::Value>().await?;
|
||||
assert_eq!(fileset_val["config.yaml"], "key: value");
|
||||
assert_eq!(fileset_val["data/input.json"], "{\"items\": []}");
|
||||
resp.json::<serde_json::Value>().await?;
|
||||
|
||||
// --- resource types ---
|
||||
|
||||
@@ -410,68 +384,17 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
assert_eq!(body["description"], "Updated type desc");
|
||||
|
||||
// type/delete
|
||||
let resp = authed(client().delete(resource_url(port, "type/delete", "new_test_type")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let resp = authed(
|
||||
client().delete(resource_url(port, "type/delete", "new_test_type")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
let resp = authed_get(port, "type/exists", "new_test_type").await;
|
||||
assert_eq!(resp.json::<bool>().await?, false);
|
||||
|
||||
// --- fileset resource type CRUD ---
|
||||
|
||||
// type/get for fileset type - verify is_fileset is returned
|
||||
let resp = authed_get(port, "type/get", "test_fileset").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body = resp.json::<serde_json::Value>().await?;
|
||||
assert_eq!(body["name"], "test_fileset");
|
||||
assert_eq!(body["is_fileset"], true);
|
||||
assert_eq!(body["format_extension"], serde_json::Value::Null);
|
||||
|
||||
// type/get for non-fileset type - verify is_fileset is false
|
||||
let resp = authed_get(port, "type/get", "test_db").await;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body = resp.json::<serde_json::Value>().await?;
|
||||
assert_eq!(body["is_fileset"], false);
|
||||
|
||||
// type/create fileset type (no format_extension needed)
|
||||
let resp = authed(client().post(format!("{base}/type/create")))
|
||||
.json(&json!({
|
||||
"name": "new_fileset_type",
|
||||
"description": "A fileset type",
|
||||
"schema": {},
|
||||
"is_fileset": true
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 201);
|
||||
|
||||
let resp = authed_get(port, "type/get", "new_fileset_type").await;
|
||||
let body = resp.json::<serde_json::Value>().await?;
|
||||
assert_eq!(body["is_fileset"], true);
|
||||
assert_eq!(body["format_extension"], serde_json::Value::Null);
|
||||
|
||||
// type/update - set is_fileset on existing type
|
||||
let resp = authed(client().post(resource_url(port, "type/update", "new_fileset_type")))
|
||||
.json(&json!({"is_fileset": false}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
let resp = authed_get(port, "type/get", "new_fileset_type").await;
|
||||
let body = resp.json::<serde_json::Value>().await?;
|
||||
assert_eq!(body["is_fileset"], false);
|
||||
|
||||
// cleanup
|
||||
let resp = authed(client().delete(resource_url(port, "type/delete", "new_fileset_type")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,9 @@ pub mod concurrency_groups;
|
||||
pub mod execution;
|
||||
pub mod job_metrics;
|
||||
pub mod jobs_export;
|
||||
pub mod negated_filter;
|
||||
pub mod query;
|
||||
pub mod types;
|
||||
|
||||
pub use execution::*;
|
||||
pub use negated_filter::{NegatedFilter, NegatedListFilter};
|
||||
pub use query::*;
|
||||
pub use types::*;
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Author: Windmill Labs, Inc
|
||||
* Copyright: Windmill Labs, Inc 2024
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
//! Filter wrappers that support an optional `!` negation prefix.
|
||||
//!
|
||||
//! - [`NegatedFilter<T>`] — a single value, e.g. `"schedule"` or `"!schedule"`.
|
||||
//! - [`NegatedListFilter<T>`] — comma-separated values, e.g. `"!schedule,!email"` or `"http,webhook"`.
|
||||
//! Every item in the list shares the same negated/non-negated sense; mixing is not supported
|
||||
|
||||
use serde::{
|
||||
de::{self, DeserializeOwned},
|
||||
Deserializer,
|
||||
};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
// ── NegatedFilter<T> ──────────────────────────────────────────────────────────
|
||||
|
||||
/// A single filter value optionally prefixed with `!` to indicate negation.
|
||||
///
|
||||
/// Deserializes `"schedule"` → `NegatedFilter { value: Schedule, negated: false }`
|
||||
/// Deserializes `"!schedule"` → `NegatedFilter { value: Schedule, negated: true }`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NegatedFilter<T> {
|
||||
pub value: T,
|
||||
pub negated: bool,
|
||||
}
|
||||
|
||||
impl<T> NegatedFilter<T> {
|
||||
pub fn positive(value: T) -> Self {
|
||||
Self { value, negated: false }
|
||||
}
|
||||
|
||||
pub fn negated(value: T) -> Self {
|
||||
Self { value, negated: true }
|
||||
}
|
||||
}
|
||||
|
||||
struct NegatedFilterVisitor<T>(PhantomData<T>);
|
||||
|
||||
impl<'de, T: DeserializeOwned> de::Visitor<'de> for NegatedFilterVisitor<T> {
|
||||
type Value = NegatedFilter<T>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "a string optionally prefixed with '!'")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||
let (negated, raw) = match s.strip_prefix('!') {
|
||||
Some(rest) => (true, rest),
|
||||
None => (false, s),
|
||||
};
|
||||
let value = serde_json::from_value(serde_json::Value::String(raw.to_owned()))
|
||||
.map_err(|e| E::custom(format!("invalid filter value {:?}: {}", raw, e)))?;
|
||||
Ok(NegatedFilter { value, negated })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> de::Deserialize<'de> for NegatedFilter<T> {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_str(NegatedFilterVisitor(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
// ── NegatedListFilter<T> ──────────────────────────────────────────────────────
|
||||
|
||||
/// A comma-separated list of filter values, all sharing the same negation sense.
|
||||
///
|
||||
/// Deserializes `"schedule,email"` → `NegatedListFilter { values: [Schedule, Email], negated: false }`
|
||||
/// Deserializes `"!schedule,!email"` → `NegatedListFilter { values: [Schedule, Email], negated: true }`
|
||||
///
|
||||
/// The `!` is read from the **first** item only; subsequent items may or may not carry
|
||||
/// `!` and it is stripped regardless, keeping the API forgiving.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NegatedListFilter<T> {
|
||||
pub values: Vec<T>,
|
||||
pub negated: bool,
|
||||
}
|
||||
|
||||
impl<T> NegatedListFilter<T> {
|
||||
pub fn positive(values: Vec<T>) -> Self {
|
||||
Self { values, negated: false }
|
||||
}
|
||||
}
|
||||
|
||||
struct NegatedListFilterVisitor<T>(PhantomData<T>);
|
||||
|
||||
impl<'de, T: DeserializeOwned> de::Visitor<'de> for NegatedListFilterVisitor<T> {
|
||||
type Value = NegatedListFilter<T>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "a comma-separated string optionally prefixed with '!'")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||
let mut negated = false;
|
||||
let values = s
|
||||
.split(',')
|
||||
.enumerate()
|
||||
.map(|(i, item)| {
|
||||
let raw = match item.strip_prefix('!') {
|
||||
Some(rest) => {
|
||||
if i == 0 {
|
||||
negated = true;
|
||||
}
|
||||
rest
|
||||
}
|
||||
None => item,
|
||||
};
|
||||
serde_json::from_value::<T>(serde_json::Value::String(raw.to_owned()))
|
||||
.map_err(|e| E::custom(format!("invalid filter value {:?}: {}", raw, e)))
|
||||
})
|
||||
.collect::<Result<Vec<T>, E>>()?;
|
||||
Ok(NegatedListFilter { values, negated })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: DeserializeOwned> de::Deserialize<'de> for NegatedListFilter<T> {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_str(NegatedListFilterVisitor(PhantomData))
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,6 @@ use windmill_common::utils::{paginate_without_limits, Pagination};
|
||||
|
||||
use crate::types::{ListCompletedQuery, ListQueueQuery};
|
||||
|
||||
/// Build a `NOT IN (...)` clause that also includes `OR col IS NULL`, so that
|
||||
/// rows where the nullable column is NULL are not silently excluded.
|
||||
fn not_in_nullable(col: &str, quoted: &[String]) -> String {
|
||||
format!(
|
||||
"({} IS NULL OR {} NOT IN ({}))",
|
||||
col,
|
||||
col,
|
||||
quoted.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn filter_list_queue_query(
|
||||
mut sqlb: SqlBuilder,
|
||||
lq: &ListQueueQuery,
|
||||
@@ -44,62 +33,18 @@ pub fn filter_list_queue_query(
|
||||
}
|
||||
|
||||
if let Some(w) = &lq.worker {
|
||||
let quoted: Vec<_> = w.values.iter().map(|v| quote(v)).collect();
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let clauses: Vec<_> = w
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let p = v.replace("*", "%").replace("'", "''");
|
||||
if w.negated {
|
||||
format!("v2_job_queue.worker NOT LIKE '{p}'")
|
||||
} else {
|
||||
format!("v2_job_queue.worker LIKE '{p}'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if w.negated { " AND " } else { " OR " };
|
||||
let inner = clauses.join(sep);
|
||||
if w.negated {
|
||||
sqlb.and_where(format!("(v2_job_queue.worker IS NULL OR ({inner}))"));
|
||||
} else {
|
||||
sqlb.and_where(format!("({inner})"));
|
||||
}
|
||||
} else if w.negated {
|
||||
sqlb.and_where(not_in_nullable("v2_job_queue.worker", "ed));
|
||||
sqlb.and_where_like_left("v2_job_queue.worker", w.replace("*", "%"));
|
||||
} else {
|
||||
sqlb.and_where_in("v2_job_queue.worker", "ed);
|
||||
sqlb.and_where_eq("v2_job_queue.worker", "?".bind(w));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ps) = &lq.script_path_start {
|
||||
let clauses: Vec<_> = ps
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let e = v.replace("'", "''");
|
||||
if ps.negated {
|
||||
format!("runnable_path NOT LIKE '{e}%'")
|
||||
} else {
|
||||
format!("runnable_path LIKE '{e}%'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if ps.negated { " AND " } else { " OR " };
|
||||
let inner = clauses.join(sep);
|
||||
if ps.negated {
|
||||
sqlb.and_where(format!("(runnable_path IS NULL OR ({inner}))"));
|
||||
} else {
|
||||
sqlb.and_where(format!("({inner})"));
|
||||
}
|
||||
sqlb.and_where_like_left("runnable_path", ps);
|
||||
}
|
||||
if let Some(p) = &lq.script_path_exact {
|
||||
let quoted: Vec<_> = p.values.iter().map(|v| quote(v)).collect();
|
||||
if p.negated {
|
||||
sqlb.and_where(not_in_nullable("runnable_path", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("runnable_path", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("runnable_path", "?".bind(p));
|
||||
}
|
||||
if let Some(p) = &lq.schedule_path {
|
||||
sqlb.and_where_eq("trigger", "?".bind(p));
|
||||
@@ -109,34 +54,13 @@ pub fn filter_list_queue_query(
|
||||
sqlb.and_where_eq("runnable_id", "?".bind(h));
|
||||
}
|
||||
if let Some(cb) = &lq.created_by {
|
||||
let quoted: Vec<_> = cb.values.iter().map(|v| quote(v)).collect();
|
||||
if cb.negated {
|
||||
sqlb.and_where_not_in("created_by", "ed);
|
||||
} else {
|
||||
sqlb.and_where_in("created_by", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("created_by", "?".bind(cb));
|
||||
}
|
||||
if let Some(t) = &lq.tag {
|
||||
let quoted: Vec<_> = t.values.iter().map(|v| quote(v)).collect();
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let clauses: Vec<_> = t
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let p = v.replace("*", "%").replace("'", "''");
|
||||
if t.negated {
|
||||
format!("v2_job.tag NOT LIKE '{p}'")
|
||||
} else {
|
||||
format!("v2_job.tag LIKE '{p}'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if t.negated { " AND " } else { " OR " };
|
||||
sqlb.and_where(format!("({})", clauses.join(sep)));
|
||||
} else if t.negated {
|
||||
sqlb.and_where_not_in("v2_job.tag", "ed);
|
||||
sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%"));
|
||||
} else {
|
||||
sqlb.and_where_in("v2_job.tag", "ed);
|
||||
sqlb.and_where_eq("v2_job.tag", "?".bind(t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,12 +115,10 @@ pub fn filter_list_queue_query(
|
||||
}
|
||||
|
||||
if let Some(jk) = &lq.job_kinds {
|
||||
let quoted: Vec<_> = jk.values.iter().map(|v| quote(v)).collect();
|
||||
if jk.negated {
|
||||
sqlb.and_where_not_in("kind", "ed);
|
||||
} else {
|
||||
sqlb.and_where_in("kind", "ed);
|
||||
}
|
||||
sqlb.and_where_in(
|
||||
"kind",
|
||||
&jk.split(',').into_iter().map(quote).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
@@ -212,21 +134,11 @@ pub fn filter_list_queue_query(
|
||||
}
|
||||
|
||||
if let Some(tk) = &lq.trigger_kind {
|
||||
let quoted: Vec<_> = tk.values.iter().map(|v| quote(&format!("{}", v))).collect();
|
||||
if tk.negated {
|
||||
sqlb.and_where(not_in_nullable("trigger_kind", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("trigger_kind", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk)));
|
||||
}
|
||||
|
||||
if let Some(tp) = &lq.trigger_path {
|
||||
let quoted: Vec<_> = tp.values.iter().map(|v| quote(v)).collect();
|
||||
if tp.negated {
|
||||
sqlb.and_where(not_in_nullable("trigger", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("trigger", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("trigger", "?".bind(tp));
|
||||
}
|
||||
|
||||
sqlb
|
||||
@@ -275,71 +187,25 @@ pub fn filter_list_completed_query(
|
||||
|
||||
if let Some(label) = &lq.label {
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let clauses: Vec<_> = label
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let p = v.replace("*", "%").replace("'", "''");
|
||||
if label.negated {
|
||||
format!(
|
||||
"NOT EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') lbl WHERE jsonb_typeof(result->'wm_labels') = 'array' AND lbl LIKE '{p}')"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') lbl WHERE jsonb_typeof(result->'wm_labels') = 'array' AND lbl LIKE '{p}')"
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if label.negated { " AND " } else { " OR " };
|
||||
if !label.negated {
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
}
|
||||
sqlb.and_where(format!("({})", clauses.join(sep)));
|
||||
} else if label.negated {
|
||||
let clauses: Vec<_> = label
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| format!("NOT (result->'wm_labels' ? '{}')", v.replace("'", "''")))
|
||||
.collect();
|
||||
sqlb.and_where(format!("({})", clauses.join(" AND ")));
|
||||
} else {
|
||||
let clauses: Vec<_> = label
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| format!("result->'wm_labels' ? '{}'", v.replace("'", "''")))
|
||||
.collect();
|
||||
let wh = format!(
|
||||
"EXISTS (SELECT 1 FROM jsonb_array_elements_text(result->'wm_labels') label WHERE jsonb_typeof(result->'wm_labels') = 'array' AND label LIKE '{}')",
|
||||
&label.replace("*", "%").replace("'", "''")
|
||||
);
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
sqlb.and_where(format!("({})", clauses.join(" OR ")));
|
||||
sqlb.and_where(&wh);
|
||||
} else {
|
||||
let mut wh = format!("result->'wm_labels' ? ");
|
||||
wh.push_str(&format!("'{}'", &label.replace("'", "''")));
|
||||
sqlb.and_where("result ? 'wm_labels'");
|
||||
sqlb.and_where(&wh);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(worker) = &lq.worker {
|
||||
let quoted: Vec<_> = worker.values.iter().map(|v| quote(v)).collect();
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let clauses: Vec<_> = worker
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let p = v.replace("*", "%").replace("'", "''");
|
||||
if worker.negated {
|
||||
format!("v2_job_completed.worker NOT LIKE '{p}'")
|
||||
} else {
|
||||
format!("v2_job_completed.worker LIKE '{p}'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if worker.negated { " AND " } else { " OR " };
|
||||
let inner = clauses.join(sep);
|
||||
if worker.negated {
|
||||
sqlb.and_where(format!("(v2_job_completed.worker IS NULL OR ({inner}))"));
|
||||
} else {
|
||||
sqlb.and_where(format!("({inner})"));
|
||||
}
|
||||
} else if worker.negated {
|
||||
sqlb.and_where(not_in_nullable("v2_job_completed.worker", "ed));
|
||||
sqlb.and_where_like_left("v2_job_completed.worker", worker.replace("*", "%"));
|
||||
} else {
|
||||
sqlb.and_where_in("v2_job_completed.worker", "ed);
|
||||
sqlb.and_where_eq("v2_job_completed.worker", "?".bind(worker));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,68 +220,24 @@ pub fn filter_list_completed_query(
|
||||
}
|
||||
|
||||
if let Some(ps) = &lq.script_path_start {
|
||||
let clauses: Vec<_> = ps
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let e = v.replace("'", "''");
|
||||
if ps.negated {
|
||||
format!("runnable_path NOT LIKE '{e}%'")
|
||||
} else {
|
||||
format!("runnable_path LIKE '{e}%'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if ps.negated { " AND " } else { " OR " };
|
||||
let inner = clauses.join(sep);
|
||||
if ps.negated {
|
||||
sqlb.and_where(format!("(runnable_path IS NULL OR ({inner}))"));
|
||||
} else {
|
||||
sqlb.and_where(format!("({inner})"));
|
||||
}
|
||||
sqlb.and_where_like_left("runnable_path", ps);
|
||||
}
|
||||
if let Some(p) = &lq.script_path_exact {
|
||||
let quoted: Vec<_> = p.values.iter().map(|v| quote(v)).collect();
|
||||
if p.negated {
|
||||
sqlb.and_where(not_in_nullable("runnable_path", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("runnable_path", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("runnable_path", "?".bind(p));
|
||||
}
|
||||
if let Some(h) = &lq.script_hash {
|
||||
sqlb.and_where_eq("runnable_id", "?".bind(h));
|
||||
}
|
||||
if let Some(t) = &lq.tag {
|
||||
let quoted: Vec<_> = t.values.iter().map(|v| quote(v)).collect();
|
||||
if lq.allow_wildcards.unwrap_or(false) {
|
||||
let clauses: Vec<_> = t
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let p = v.replace("*", "%").replace("'", "''");
|
||||
if t.negated {
|
||||
format!("v2_job.tag NOT LIKE '{p}'")
|
||||
} else {
|
||||
format!("v2_job.tag LIKE '{p}'")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let sep = if t.negated { " AND " } else { " OR " };
|
||||
sqlb.and_where(format!("({})", clauses.join(sep)));
|
||||
} else if t.negated {
|
||||
sqlb.and_where_not_in("v2_job.tag", "ed);
|
||||
sqlb.and_where_like_left("v2_job.tag", t.replace("*", "%"));
|
||||
} else {
|
||||
sqlb.and_where_in("v2_job.tag", "ed);
|
||||
sqlb.and_where_eq("v2_job.tag", "?".bind(t));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cb) = &lq.created_by {
|
||||
let quoted: Vec<_> = cb.values.iter().map(|v| quote(v)).collect();
|
||||
if cb.negated {
|
||||
sqlb.and_where_not_in("created_by", "ed);
|
||||
} else {
|
||||
sqlb.and_where_in("created_by", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("created_by", "?".bind(cb));
|
||||
}
|
||||
if let Some(r) = &lq.success {
|
||||
if *r {
|
||||
@@ -486,12 +308,10 @@ pub fn filter_list_completed_query(
|
||||
}
|
||||
}
|
||||
if let Some(jk) = &lq.job_kinds {
|
||||
let quoted: Vec<_> = jk.values.iter().map(|v| quote(v)).collect();
|
||||
if jk.negated {
|
||||
sqlb.and_where_not_in("kind", "ed);
|
||||
} else {
|
||||
sqlb.and_where_in("kind", "ed);
|
||||
}
|
||||
sqlb.and_where_in(
|
||||
"kind",
|
||||
&jk.split(',').into_iter().map(quote).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
@@ -507,21 +327,11 @@ pub fn filter_list_completed_query(
|
||||
}
|
||||
|
||||
if let Some(tk) = &lq.trigger_kind {
|
||||
let quoted: Vec<_> = tk.values.iter().map(|v| quote(&format!("{}", v))).collect();
|
||||
if tk.negated {
|
||||
sqlb.and_where(not_in_nullable("trigger_kind", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("trigger_kind", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk)));
|
||||
}
|
||||
|
||||
if let Some(tp) = &lq.trigger_path {
|
||||
let quoted: Vec<_> = tp.values.iter().map(|v| quote(v)).collect();
|
||||
if tp.negated {
|
||||
sqlb.and_where(not_in_nullable("trigger", "ed));
|
||||
} else {
|
||||
sqlb.and_where_in("trigger", "ed);
|
||||
}
|
||||
sqlb.and_where_eq("trigger", "?".bind(tp));
|
||||
}
|
||||
|
||||
sqlb
|
||||
@@ -565,7 +375,6 @@ pub fn list_completed_jobs_query(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::negated_filter::NegatedListFilter;
|
||||
|
||||
fn empty_queue_query() -> ListQueueQuery {
|
||||
ListQueueQuery {
|
||||
@@ -669,7 +478,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_queue_filter_script_path_start() {
|
||||
let lq = ListQueueQuery {
|
||||
script_path_start: Some(NegatedListFilter::positive(vec!["f/test".to_string()])),
|
||||
script_path_start: Some("f/test".to_string()),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
@@ -686,9 +495,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_queue_filter_script_path_exact() {
|
||||
let lq = ListQueueQuery {
|
||||
script_path_exact: Some(NegatedListFilter::positive(vec![
|
||||
"f/test/script".to_string()
|
||||
])),
|
||||
script_path_exact: Some("f/test/script".to_string()),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
@@ -703,7 +510,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_running() {
|
||||
let lq = ListQueueQuery { running: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
running: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -717,10 +527,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_queue_filter_job_kinds() {
|
||||
let lq = ListQueueQuery {
|
||||
job_kinds: Some(NegatedListFilter::positive(vec![
|
||||
"script".to_string(),
|
||||
"flow".to_string(),
|
||||
])),
|
||||
job_kinds: Some("script,flow".to_string()),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
@@ -736,7 +543,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_suspended() {
|
||||
let lq = ListQueueQuery { suspended: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
suspended: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -749,7 +559,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_is_not_schedule() {
|
||||
let lq = ListQueueQuery { is_not_schedule: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
is_not_schedule: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -762,7 +575,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_has_null_parent() {
|
||||
let lq = ListQueueQuery { has_null_parent: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
has_null_parent: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -775,7 +591,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_is_flow_step_true() {
|
||||
let lq = ListQueueQuery { is_flow_step: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
is_flow_step: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -788,7 +607,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_filter_is_flow_step_false() {
|
||||
let lq = ListQueueQuery { is_flow_step: Some(false), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
is_flow_step: Some(false),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -801,7 +623,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_admins_all_workspaces() {
|
||||
let lq = ListQueueQuery { all_workspaces: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
all_workspaces: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -814,7 +639,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_queue_non_admins_ignores_all_workspaces() {
|
||||
let lq = ListQueueQuery { all_workspaces: Some(true), ..empty_queue_query() };
|
||||
let lq = ListQueueQuery {
|
||||
all_workspaces: Some(true),
|
||||
..empty_queue_query()
|
||||
};
|
||||
let sqlb = filter_list_queue_query(
|
||||
SqlBuilder::select_from("v2_job_queue").clone(),
|
||||
&lq,
|
||||
@@ -867,7 +695,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_completed_filter_success_true() {
|
||||
let lq = ListCompletedQuery { success: Some(true), ..empty_completed_query() };
|
||||
let lq = ListCompletedQuery {
|
||||
success: Some(true),
|
||||
..empty_completed_query()
|
||||
};
|
||||
let sqlb = filter_list_completed_query(
|
||||
SqlBuilder::select_from("v2_job_completed").clone(),
|
||||
&lq,
|
||||
@@ -880,7 +711,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_completed_filter_success_false() {
|
||||
let lq = ListCompletedQuery { success: Some(false), ..empty_completed_query() };
|
||||
let lq = ListCompletedQuery {
|
||||
success: Some(false),
|
||||
..empty_completed_query()
|
||||
};
|
||||
let sqlb = filter_list_completed_query(
|
||||
SqlBuilder::select_from("v2_job_completed").clone(),
|
||||
&lq,
|
||||
@@ -905,7 +739,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_completed_filter_label() {
|
||||
let lq = ListCompletedQuery {
|
||||
label: Some(NegatedListFilter::positive(vec!["deploy".to_string()])),
|
||||
label: Some("deploy".to_string()),
|
||||
..empty_completed_query()
|
||||
};
|
||||
let sqlb = filter_list_completed_query(
|
||||
@@ -920,7 +754,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_completed_filter_is_skipped() {
|
||||
let lq = ListCompletedQuery { is_skipped: Some(true), ..empty_completed_query() };
|
||||
let lq = ListCompletedQuery {
|
||||
is_skipped: Some(true),
|
||||
..empty_completed_query()
|
||||
};
|
||||
let sqlb = filter_list_completed_query(
|
||||
SqlBuilder::select_from("v2_job_completed").clone(),
|
||||
&lq,
|
||||
|
||||
@@ -27,8 +27,6 @@ use windmill_common::{
|
||||
|
||||
use windmill_api_sse::{Job, JobExtended};
|
||||
|
||||
use crate::negated_filter::NegatedListFilter;
|
||||
|
||||
// ------------ RunJobQuery ------------
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
@@ -91,10 +89,10 @@ impl RunJobQuery {
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct ListQueueQuery {
|
||||
pub script_path_start: Option<NegatedListFilter<String>>,
|
||||
pub script_path_exact: Option<NegatedListFilter<String>>,
|
||||
pub script_path_start: Option<String>,
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<NegatedListFilter<String>>,
|
||||
pub created_by: Option<String>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
@@ -105,12 +103,12 @@ pub struct ListQueueQuery {
|
||||
pub schedule_path: Option<String>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
pub job_kinds: Option<NegatedListFilter<String>>,
|
||||
pub job_kinds: Option<String>,
|
||||
pub suspended: Option<bool>,
|
||||
pub worker: Option<NegatedListFilter<String>>,
|
||||
pub worker: Option<String>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
pub tag: Option<NegatedListFilter<String>>,
|
||||
pub tag: Option<String>,
|
||||
pub scheduled_for_before_now: Option<bool>,
|
||||
pub all_workspaces: Option<bool>,
|
||||
pub is_flow_step: Option<bool>,
|
||||
@@ -118,17 +116,17 @@ pub struct ListQueueQuery {
|
||||
pub is_not_schedule: Option<bool>,
|
||||
pub concurrency_key: Option<String>,
|
||||
pub allow_wildcards: Option<bool>,
|
||||
pub trigger_kind: Option<NegatedListFilter<JobTriggerKind>>,
|
||||
pub trigger_path: Option<NegatedListFilter<String>>,
|
||||
pub trigger_kind: Option<JobTriggerKind>,
|
||||
pub trigger_path: Option<String>,
|
||||
pub include_args: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct ListCompletedQuery {
|
||||
pub script_path_start: Option<NegatedListFilter<String>>,
|
||||
pub script_path_exact: Option<NegatedListFilter<String>>,
|
||||
pub script_path_start: Option<String>,
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<NegatedListFilter<String>>,
|
||||
pub created_by: Option<String>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
@@ -144,7 +142,7 @@ pub struct ListCompletedQuery {
|
||||
pub running: Option<bool>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
pub job_kinds: Option<NegatedListFilter<String>>,
|
||||
pub job_kinds: Option<String>,
|
||||
pub is_skipped: Option<bool>,
|
||||
pub is_flow_step: Option<bool>,
|
||||
pub suspended: Option<bool>,
|
||||
@@ -153,17 +151,17 @@ pub struct ListCompletedQuery {
|
||||
pub args: Option<String>,
|
||||
// filter by matching a subset of the result using base64 encoded json subset
|
||||
pub result: Option<String>,
|
||||
pub tag: Option<NegatedListFilter<String>>,
|
||||
pub tag: Option<String>,
|
||||
pub scheduled_for_before_now: Option<bool>,
|
||||
pub all_workspaces: Option<bool>,
|
||||
pub has_null_parent: Option<bool>,
|
||||
pub label: Option<NegatedListFilter<String>>,
|
||||
pub label: Option<String>,
|
||||
pub is_not_schedule: Option<bool>,
|
||||
pub concurrency_key: Option<String>,
|
||||
pub worker: Option<NegatedListFilter<String>>,
|
||||
pub worker: Option<String>,
|
||||
pub allow_wildcards: Option<bool>,
|
||||
pub trigger_kind: Option<NegatedListFilter<JobTriggerKind>>,
|
||||
pub trigger_path: Option<NegatedListFilter<String>>,
|
||||
pub trigger_kind: Option<JobTriggerKind>,
|
||||
pub trigger_path: Option<String>,
|
||||
pub include_args: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -580,7 +578,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_decode_payload_valid() {
|
||||
let payload = base64::engine::general_purpose::STANDARD.encode(r#"{"key": "value"}"#);
|
||||
let payload = base64::engine::general_purpose::STANDARD
|
||||
.encode(r#"{"key": "value"}"#);
|
||||
let result: HashMap<String, serde_json::Value> = decode_payload(payload).unwrap();
|
||||
assert_eq!(result["key"], json!("value"));
|
||||
}
|
||||
@@ -645,15 +644,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_run_job_query_payload_as_args_valid() {
|
||||
let encoded = base64::engine::general_purpose::STANDARD.encode(r#"{"x": 42}"#);
|
||||
let q = RunJobQuery { payload: Some(encoded), ..Default::default() };
|
||||
let encoded = base64::engine::general_purpose::STANDARD
|
||||
.encode(r#"{"x": 42}"#);
|
||||
let q = RunJobQuery {
|
||||
payload: Some(encoded),
|
||||
..Default::default()
|
||||
};
|
||||
let result = q.payload_as_args().unwrap();
|
||||
assert!(result.contains_key("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_job_query_payload_as_args_invalid() {
|
||||
let q = RunJobQuery { payload: Some("invalid!!!".to_string()), ..Default::default() };
|
||||
let q = RunJobQuery {
|
||||
payload: Some("invalid!!!".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(q.payload_as_args().is_err());
|
||||
}
|
||||
|
||||
@@ -662,10 +668,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_list_completed_to_queue_query_conversion() {
|
||||
let lcq = ListCompletedQuery {
|
||||
script_path_start: Some(NegatedListFilter::positive(vec!["f/test".to_string()])),
|
||||
script_path_start: Some("f/test".to_string()),
|
||||
script_path_exact: None,
|
||||
script_hash: None,
|
||||
created_by: Some(NegatedListFilter::positive(vec!["admin".to_string()])),
|
||||
created_by: Some("admin".to_string()),
|
||||
started_before: None,
|
||||
started_after: None,
|
||||
created_before: Some(chrono::Utc::now()),
|
||||
@@ -681,17 +687,14 @@ mod tests {
|
||||
running: Some(true),
|
||||
parent_job: None,
|
||||
order_desc: Some(true),
|
||||
job_kinds: Some(NegatedListFilter::positive(vec![
|
||||
"script".to_string(),
|
||||
"flow".to_string(),
|
||||
])),
|
||||
job_kinds: Some("script,flow".to_string()),
|
||||
is_skipped: None,
|
||||
is_flow_step: None,
|
||||
suspended: None,
|
||||
schedule_path: None,
|
||||
args: None,
|
||||
result: None,
|
||||
tag: Some(NegatedListFilter::positive(vec!["custom".to_string()])),
|
||||
tag: Some("custom".to_string()),
|
||||
scheduled_for_before_now: None,
|
||||
all_workspaces: None,
|
||||
has_null_parent: None,
|
||||
@@ -706,24 +709,11 @@ mod tests {
|
||||
};
|
||||
|
||||
let lqq: ListQueueQuery = lcq.into();
|
||||
assert_eq!(
|
||||
lqq.script_path_start
|
||||
.as_ref()
|
||||
.and_then(|f| f.values.first().cloned()),
|
||||
Some("f/test".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
lqq.created_by
|
||||
.as_ref()
|
||||
.and_then(|f| f.values.first().cloned()),
|
||||
Some("admin".to_string())
|
||||
);
|
||||
assert_eq!(lqq.script_path_start, Some("f/test".to_string()));
|
||||
assert_eq!(lqq.created_by, Some("admin".to_string()));
|
||||
assert_eq!(lqq.running, Some(true));
|
||||
assert_eq!(lqq.job_kinds.as_ref().map(|f| f.values.len()), Some(2));
|
||||
assert_eq!(
|
||||
lqq.tag.as_ref().and_then(|f| f.values.first().cloned()),
|
||||
Some("custom".to_string())
|
||||
);
|
||||
assert_eq!(lqq.job_kinds, Some("script,flow".to_string()));
|
||||
assert_eq!(lqq.tag, Some("custom".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use windmill_api_auth::{check_scopes, maybe_refresh_folders, require_super_admin, ApiAuthed};
|
||||
use windmill_common::DB;
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{delete, get, post},
|
||||
@@ -16,12 +18,9 @@ use serde::{Deserialize, Serialize};
|
||||
use sql_builder::{prelude::Bind, SqlBuilder};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use std::str::FromStr;
|
||||
use windmill_api_auth::{check_scopes, maybe_refresh_folders, require_super_admin, ApiAuthed};
|
||||
use windmill_audit::audit_oss::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::DB;
|
||||
use windmill_common::{
|
||||
can_preserve_on_behalf_of,
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
schedule::Schedule,
|
||||
@@ -31,45 +30,6 @@ use windmill_common::{
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
use windmill_queue::schedule::push_scheduled_job;
|
||||
|
||||
/// Resolves the email to use for a schedule based on preservation settings.
|
||||
/// When preserving, looks up the email from the provided username.
|
||||
async fn resolve_email(
|
||||
username: Option<&String>,
|
||||
preserve_email: Option<bool>,
|
||||
authed: &ApiAuthed,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
) -> Result<String> {
|
||||
if let Some(username) = username {
|
||||
if preserve_email.unwrap_or(false) && can_preserve_on_behalf_of(authed) {
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
username,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
if let Some(email) = email {
|
||||
return Ok(email);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(authed.email.clone())
|
||||
}
|
||||
|
||||
fn resolve_edited_by(
|
||||
username: Option<&String>,
|
||||
preserve_edited_by: Option<bool>,
|
||||
authed: &ApiAuthed,
|
||||
) -> String {
|
||||
if let Some(username) = username {
|
||||
if preserve_edited_by.unwrap_or(false) && can_preserve_on_behalf_of(authed) {
|
||||
return username.clone();
|
||||
}
|
||||
}
|
||||
authed.username.clone()
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_schedule))
|
||||
@@ -115,8 +75,6 @@ pub struct NewSchedule {
|
||||
pub paused_until: Option<DateTime<Utc>>,
|
||||
pub cron_version: Option<String>,
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub preserve_email: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -242,8 +200,6 @@ async fn create_schedule(
|
||||
validate_dynamic_skip(&mut tx, &w_id, handler_path).await?;
|
||||
}
|
||||
|
||||
let resolved_edited_by = resolve_edited_by(ns.email.as_ref(), ns.preserve_email, &authed);
|
||||
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
r#"
|
||||
@@ -301,13 +257,13 @@ async fn create_schedule(
|
||||
ns.path,
|
||||
ns.schedule,
|
||||
ns.timezone,
|
||||
resolved_edited_by,
|
||||
authed.username,
|
||||
ns.script_path,
|
||||
ns.is_flow,
|
||||
to_json_raw_opt(ns.args.as_ref())
|
||||
as Option<sqlx::types::Json<Box<serde_json::value::RawValue>>>,
|
||||
ns.enabled.unwrap_or(false),
|
||||
resolve_email(ns.email.as_ref(), ns.preserve_email, &authed, &db, &w_id).await?,
|
||||
authed.email,
|
||||
ns.on_failure,
|
||||
ns.on_failure_times,
|
||||
ns.on_failure_exact,
|
||||
@@ -352,29 +308,6 @@ async fn create_schedule(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
ns.email.as_deref(),
|
||||
ns.preserve_email.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"schedule.on_behalf_of",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(&ns.path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "create"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if ns.enabled.unwrap_or(true) {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, Some(&authed.clone().into()), None).await?
|
||||
@@ -418,9 +351,6 @@ async fn edit_schedule(
|
||||
}
|
||||
|
||||
clear_schedule(&mut tx, path, &w_id).await?;
|
||||
|
||||
let resolved_edited_by = resolve_edited_by(es.email.as_ref(), es.preserve_email, &authed);
|
||||
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
r#"
|
||||
@@ -447,9 +377,7 @@ async fn edit_schedule(
|
||||
workspace_id = $20,
|
||||
cron_version = COALESCE($21, cron_version),
|
||||
description = $22,
|
||||
dynamic_skip = $23,
|
||||
email = COALESCE($24, email),
|
||||
edited_by = $25
|
||||
dynamic_skip = $23
|
||||
WHERE path = $19 AND workspace_id = $20
|
||||
RETURNING
|
||||
workspace_id,
|
||||
@@ -510,9 +438,7 @@ async fn edit_schedule(
|
||||
w_id,
|
||||
es.cron_version,
|
||||
es.description,
|
||||
es.dynamic_skip,
|
||||
Some(resolve_email(es.email.as_ref(), es.preserve_email, &authed, &db, &w_id).await?),
|
||||
resolved_edited_by
|
||||
es.dynamic_skip
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
@@ -533,29 +459,6 @@ async fn edit_schedule(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
es.email.as_deref(),
|
||||
es.preserve_email.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.username,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"schedule.on_behalf_of",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "update"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if schedule.enabled {
|
||||
tx = push_scheduled_job(&db, tx, &schedule, None, None).await?;
|
||||
@@ -583,15 +486,8 @@ pub struct ListScheduleQuery {
|
||||
pub per_page: Option<usize>,
|
||||
pub path: Option<String>,
|
||||
pub is_flow: Option<bool>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
pub path_start: Option<String>,
|
||||
// exact match on schedule path
|
||||
pub schedule_path: Option<String>,
|
||||
// filter on description (pattern match)
|
||||
pub description: Option<String>,
|
||||
// filter on summary (pattern match)
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -647,18 +543,6 @@ async fn list_schedule(
|
||||
if let Some(path_start) = &lsq.path_start {
|
||||
sqlb.and_where_like_left("path", path_start);
|
||||
}
|
||||
if let Some(schedule_path) = &lsq.schedule_path {
|
||||
sqlb.and_where_eq("path", "?".bind(schedule_path));
|
||||
}
|
||||
if let Some(description) = &lsq.description {
|
||||
sqlb.and_where(&format!(
|
||||
"description ILIKE '%{}%'",
|
||||
description.replace("'", "''")
|
||||
));
|
||||
}
|
||||
if let Some(summary) = &lsq.summary {
|
||||
sqlb.and_where(&format!("summary ILIKE '%{}%'", summary.replace("'", "''")));
|
||||
}
|
||||
let sql = sqlb.sql().map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let rows = sqlx::query_as::<_, ScheduleLight>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -1170,8 +1054,6 @@ pub struct EditSchedule {
|
||||
pub paused_until: Option<DateTime<Utc>>,
|
||||
pub cron_version: Option<String>,
|
||||
pub dynamic_skip: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub preserve_email: Option<bool>,
|
||||
}
|
||||
|
||||
pub use windmill_queue::schedule::clear_schedule;
|
||||
|
||||
@@ -927,11 +927,11 @@ async fn create_script_internal<'c>(
|
||||
no_main_func.filter(|x: &bool| *x), // should be Some(true) or None
|
||||
codebase,
|
||||
has_preprocessor.filter(|x: &bool| *x), // should be Some(true) or None
|
||||
windmill_common::resolve_on_behalf_of_email(
|
||||
ns.on_behalf_of_email.as_deref(),
|
||||
ns.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
),
|
||||
if ns.on_behalf_of_email.is_some() {
|
||||
Some(&authed.email)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
validate_schema,
|
||||
ns.assets.as_ref().and_then(|a| serde_json::to_value(a).ok()),
|
||||
guarded_debounce_key,
|
||||
@@ -1027,29 +1027,6 @@ async fn create_script_internal<'c>(
|
||||
Some([("hash", hash.to_string().as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
ns.on_behalf_of_email.as_deref(),
|
||||
ns.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.email,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"scripts.on_behalf_of",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&ns.path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "update"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateScript {
|
||||
@@ -1075,29 +1052,6 @@ async fn create_script_internal<'c>(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation(
|
||||
ns.on_behalf_of_email.as_deref(),
|
||||
ns.preserve_on_behalf_of.unwrap_or(false),
|
||||
&authed,
|
||||
&authed.email,
|
||||
) {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"scripts.on_behalf_of",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(&ns.path),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "create"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateScript {
|
||||
|
||||
@@ -12,24 +12,24 @@ use std::{collections::HashMap, time::Duration};
|
||||
mod ee;
|
||||
pub mod ee_oss;
|
||||
|
||||
use windmill_api_auth::{require_super_admin, ApiAuthed};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_api_auth::require_devops_role;
|
||||
use windmill_api_auth::{require_super_admin, ApiAuthed};
|
||||
use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
use windmill_common::DB;
|
||||
|
||||
use ee_oss::validate_license_key;
|
||||
use windmill_common::usernames::generate_instance_username_for_all_users;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::extract::Query;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Extension, Path},
|
||||
response::Response,
|
||||
routing::{get, post},
|
||||
body::Body,
|
||||
response::Response,
|
||||
Json, Router,
|
||||
};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::extract::Query;
|
||||
use serde_json::json;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -43,8 +43,9 @@ use windmill_common::{
|
||||
get_database_url,
|
||||
global_settings::{
|
||||
APP_WORKSPACED_ROUTE_SETTING, AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
CRITICAL_ALERT_MUTE_UI_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING,
|
||||
ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING,
|
||||
CRITICAL_ALERT_MUTE_UI_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
|
||||
EMAIL_DOMAIN_SETTING, ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING,
|
||||
},
|
||||
instance_config::{self, ApplyMode, InstanceConfig},
|
||||
server::Smtp,
|
||||
@@ -170,9 +171,7 @@ pub async fn test_s3_bucket(
|
||||
.await?
|
||||
.store;
|
||||
|
||||
let mut list = client.list(Some(
|
||||
&windmill_object_store::object_store_reexports::Path::from("".to_string()),
|
||||
));
|
||||
let mut list = client.list(Some(&windmill_object_store::object_store_reexports::Path::from("".to_string())));
|
||||
let first_file = list.next().await;
|
||||
if first_file.is_some() {
|
||||
if let Err(e) = first_file.as_ref().unwrap() {
|
||||
@@ -190,10 +189,7 @@ pub async fn test_s3_bucket(
|
||||
));
|
||||
tracing::info!("Testing blob storage at path: {path}");
|
||||
client
|
||||
.put(
|
||||
&path,
|
||||
windmill_object_store::object_store_reexports::PutPayload::from_static(b"hello"),
|
||||
)
|
||||
.put(&path, windmill_object_store::object_store_reexports::PutPayload::from_static(b"hello"))
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("error writing file to {path}: {e:#}"))?;
|
||||
let content = client
|
||||
@@ -294,17 +290,17 @@ pub async fn set_global_setting_internal(
|
||||
match value {
|
||||
serde_json::Value::Null => {
|
||||
if instance_config::PROTECTED_SETTINGS.contains(&key.as_str()) {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"{key} is a protected setting and cannot be deleted"
|
||||
)));
|
||||
return Err(error::Error::BadRequest(
|
||||
format!("{key} is a protected setting and cannot be deleted"),
|
||||
));
|
||||
}
|
||||
delete_global_setting(db, &key).await?;
|
||||
}
|
||||
serde_json::Value::String(x) if x.is_empty() => {
|
||||
if instance_config::PROTECTED_SETTINGS.contains(&key.as_str()) {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"{key} is a protected setting and cannot be set to empty"
|
||||
)));
|
||||
return Err(error::Error::BadRequest(
|
||||
format!("{key} is a protected setting and cannot be set to empty"),
|
||||
));
|
||||
}
|
||||
delete_global_setting(db, &key).await?;
|
||||
}
|
||||
@@ -441,8 +437,7 @@ async fn get_instance_config_yaml(
|
||||
let config = InstanceConfig::from_db(&db)
|
||||
.await
|
||||
.map_err(|e| error::Error::internal_err(e.to_string()))?;
|
||||
let yaml = config
|
||||
.to_sorted_yaml()
|
||||
let yaml = config.to_sorted_yaml()
|
||||
.map_err(|e| error::Error::internal_err(e))?;
|
||||
Response::builder()
|
||||
.header("content-type", "application/yaml")
|
||||
@@ -483,7 +478,8 @@ async fn set_instance_config(
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.clone(),
|
||||
serde_json::to_value(v).expect("WorkerGroupConfig serialization cannot fail"),
|
||||
serde_json::to_value(v)
|
||||
.expect("WorkerGroupConfig serialization cannot fail"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@@ -493,7 +489,8 @@ async fn set_instance_config(
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.clone(),
|
||||
serde_json::to_value(v).expect("WorkerGroupConfig serialization cannot fail"),
|
||||
serde_json::to_value(v)
|
||||
.expect("WorkerGroupConfig serialization cannot fail"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@@ -947,8 +944,7 @@ async fn setup_custom_instance_pg_database_inner(
|
||||
GRANT CREATE ON DATABASE \"{dbname}\" TO custom_instance_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;
|
||||
ALTER ROLE custom_instance_user CREATEROLE;
|
||||
ALTER ROLE custom_instance_user REPLICATION;"
|
||||
ALTER ROLE custom_instance_user CREATEROLE;"
|
||||
))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -1087,16 +1083,19 @@ async fn sync_cached_resource_types(
|
||||
use windmill_common::worker::HUB_RT_CACHE_DIR;
|
||||
let cache_path = format!("{}/resource_types.json", HUB_RT_CACHE_DIR);
|
||||
|
||||
let content = tokio::fs::read_to_string(&cache_path).await.map_err(|e| {
|
||||
error::Error::NotFound(format!(
|
||||
"No cached resource types found at {}: {}",
|
||||
cache_path, e
|
||||
))
|
||||
})?;
|
||||
let content = tokio::fs::read_to_string(&cache_path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error::Error::NotFound(format!(
|
||||
"No cached resource types found at {}: {}",
|
||||
cache_path, e
|
||||
))
|
||||
})?;
|
||||
|
||||
let cached_types: Vec<CachedResourceType> = serde_json::from_str(&content).map_err(|e| {
|
||||
error::Error::InternalErr(format!("Failed to parse cached resource types: {}", e))
|
||||
})?;
|
||||
let cached_types: Vec<CachedResourceType> =
|
||||
serde_json::from_str(&content).map_err(|e| {
|
||||
error::Error::InternalErr(format!("Failed to parse cached resource types: {}", e))
|
||||
})?;
|
||||
|
||||
let mut synced_count = 0;
|
||||
|
||||
@@ -1177,10 +1176,7 @@ mod tests {
|
||||
deserialized.global_settings.base_url.as_deref(),
|
||||
Some("https://windmill.example.com")
|
||||
);
|
||||
assert_eq!(
|
||||
deserialized.global_settings.retention_period_secs,
|
||||
Some(86400)
|
||||
);
|
||||
assert_eq!(deserialized.global_settings.retention_period_secs, Some(86400));
|
||||
assert_eq!(deserialized.global_settings.expose_metrics, Some(true));
|
||||
let wc = &deserialized.worker_configs["default"];
|
||||
assert_eq!(
|
||||
@@ -1212,7 +1208,9 @@ mod tests {
|
||||
let retention_pos = yaml.find("retention_period_secs:").unwrap();
|
||||
|
||||
assert!(
|
||||
base_url_pos < email_pos && email_pos < expose_pos && expose_pos < retention_pos,
|
||||
base_url_pos < email_pos
|
||||
&& email_pos < expose_pos
|
||||
&& expose_pos < retention_pos,
|
||||
"global_settings keys should be alphabetically sorted, got yaml:\n{yaml}"
|
||||
);
|
||||
}
|
||||
@@ -1222,34 +1220,22 @@ mod tests {
|
||||
let config = InstanceConfig {
|
||||
global_settings: GlobalSettings::default(),
|
||||
worker_configs: BTreeMap::from([
|
||||
(
|
||||
"gpu".to_string(),
|
||||
WorkerGroupConfig {
|
||||
init_bash: Some("echo gpu".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"native".to_string(),
|
||||
WorkerGroupConfig {
|
||||
init_bash: Some("echo native".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"default".to_string(),
|
||||
WorkerGroupConfig {
|
||||
init_bash: Some("echo default".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"alpha".to_string(),
|
||||
WorkerGroupConfig {
|
||||
init_bash: Some("echo alpha".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
("gpu".to_string(), WorkerGroupConfig {
|
||||
init_bash: Some("echo gpu".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
("native".to_string(), WorkerGroupConfig {
|
||||
init_bash: Some("echo native".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
("default".to_string(), WorkerGroupConfig {
|
||||
init_bash: Some("echo default".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
("alpha".to_string(), WorkerGroupConfig {
|
||||
init_bash: Some("echo alpha".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -1278,40 +1264,26 @@ mod tests {
|
||||
..Default::default()
|
||||
},
|
||||
worker_configs: BTreeMap::from([
|
||||
(
|
||||
"default".to_string(),
|
||||
WorkerGroupConfig {
|
||||
worker_tags: Some(vec!["deno".to_string()]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"native".to_string(),
|
||||
WorkerGroupConfig {
|
||||
init_bash: Some("echo hi".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
("default".to_string(), WorkerGroupConfig {
|
||||
worker_tags: Some(vec!["deno".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
("native".to_string(), WorkerGroupConfig {
|
||||
init_bash: Some("echo hi".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
};
|
||||
|
||||
let yaml = config.to_sorted_yaml().unwrap();
|
||||
let deserialized: InstanceConfig = serde_yml::from_str(&yaml).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
deserialized.global_settings.base_url.as_deref(),
|
||||
Some("https://rt.test")
|
||||
);
|
||||
assert_eq!(
|
||||
deserialized.global_settings.retention_period_secs,
|
||||
Some(7200)
|
||||
);
|
||||
assert_eq!(deserialized.global_settings.base_url.as_deref(), Some("https://rt.test"));
|
||||
assert_eq!(deserialized.global_settings.retention_period_secs, Some(7200));
|
||||
assert_eq!(deserialized.global_settings.expose_metrics, Some(false));
|
||||
assert_eq!(deserialized.worker_configs.len(), 2);
|
||||
assert_eq!(
|
||||
deserialized.worker_configs["default"]
|
||||
.worker_tags
|
||||
.as_deref(),
|
||||
deserialized.worker_configs["default"].worker_tags.as_deref(),
|
||||
Some(["deno".to_string()].as_slice())
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -30,6 +30,7 @@ use uuid::Uuid;
|
||||
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_types::s3::LargeFileStorage;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::variables::{build_crypt, decrypt, encrypt, WORKSPACE_CRYPT_CACHE};
|
||||
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
|
||||
@@ -54,7 +55,6 @@ use windmill_dep_map::scoped_dependency_map::{
|
||||
DependencyDependent, DependencyMap, ScopedDependencyMap,
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, handle_fork_branch_creation, DeployedObject};
|
||||
use windmill_types::s3::LargeFileStorage;
|
||||
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -2788,14 +2788,6 @@ async fn create_workspace(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO group_
|
||||
VALUES ($1, 'wm_deployers', 'Members can preserve the original author when deploying to this workspace')",
|
||||
nw.id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr_to_group
|
||||
VALUES ($1, 'all', $2)",
|
||||
@@ -3018,8 +3010,8 @@ async fn clone_resource_types(
|
||||
target_workspace_id: &str,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension, is_fileset)
|
||||
SELECT $2, name, schema, description, edited_at, created_by, format_extension, is_fileset
|
||||
"INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension)
|
||||
SELECT $2, name, schema, description, edited_at, created_by, format_extension
|
||||
FROM resource_type
|
||||
WHERE workspace_id = $1",
|
||||
source_workspace_id,
|
||||
@@ -3566,7 +3558,7 @@ pub(crate) async fn archive_workspace_impl(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
username: &str,
|
||||
) -> Result<(usize, usize, usize)> {
|
||||
) -> Result<(usize, usize)> {
|
||||
// Step 1: Disable all schedules and clear their queued jobs
|
||||
let mut tx = db.begin().await?;
|
||||
let disabled_schedules = sqlx::query_scalar!(
|
||||
@@ -3588,20 +3580,6 @@ pub(crate) async fn archive_workspace_impl(
|
||||
windmill_queue::schedule::clear_schedule(&mut tx, schedule_path, w_id).await?;
|
||||
}
|
||||
|
||||
// Delete non-session tokens scoped to this workspace
|
||||
let deleted_tokens = sqlx::query_scalar!(
|
||||
"DELETE FROM token WHERE workspace_id = $1 AND label IS DISTINCT FROM 'session' RETURNING token",
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"Deleted {} non-session tokens in workspace {}",
|
||||
deleted_tokens.len(),
|
||||
w_id
|
||||
);
|
||||
|
||||
// Mark workspace as archived
|
||||
sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", w_id)
|
||||
.execute(&mut *tx)
|
||||
@@ -3640,7 +3618,7 @@ pub(crate) async fn archive_workspace_impl(
|
||||
0
|
||||
};
|
||||
|
||||
Ok((schedules_count, canceled_count, deleted_tokens.len()))
|
||||
Ok((schedules_count, canceled_count))
|
||||
}
|
||||
|
||||
async fn archive_workspace(
|
||||
@@ -3650,7 +3628,7 @@ async fn archive_workspace(
|
||||
) -> Result<String> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let (schedules_count, canceled_count, deleted_tokens_count) =
|
||||
let (schedules_count, canceled_count) =
|
||||
archive_workspace_impl(&db, &w_id, &authed.username).await?;
|
||||
|
||||
// Audit log
|
||||
@@ -3658,7 +3636,6 @@ async fn archive_workspace(
|
||||
let mut audit_params = HashMap::new();
|
||||
audit_params.insert("disabled_schedules", schedules_count.to_string());
|
||||
audit_params.insert("canceled_jobs", canceled_count.to_string());
|
||||
audit_params.insert("deleted_tokens", deleted_tokens_count.to_string());
|
||||
let audit_params_refs: HashMap<&str, &str> =
|
||||
audit_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
|
||||
|
||||
@@ -3675,8 +3652,8 @@ async fn archive_workspace(
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!(
|
||||
"Archived workspace {}, disabled {} schedules, canceled {} jobs and deleted {} tokens",
|
||||
&w_id, schedules_count, canceled_count, deleted_tokens_count
|
||||
"Archived workspace {}, disabled {} schedules and canceled {} jobs",
|
||||
&w_id, schedules_count, canceled_count
|
||||
))
|
||||
}
|
||||
|
||||
@@ -5277,7 +5254,7 @@ async fn compare_two_resource_types(
|
||||
) -> Result<ItemComparison> {
|
||||
// Get resource type from each workspace
|
||||
let source_resource_type = sqlx::query!(
|
||||
"SELECT schema, description, format_extension, is_fileset
|
||||
"SELECT schema, description, format_extension
|
||||
FROM resource_type
|
||||
WHERE workspace_id = $1 AND name = $2",
|
||||
source_workspace_id,
|
||||
@@ -5287,7 +5264,7 @@ async fn compare_two_resource_types(
|
||||
.await?;
|
||||
|
||||
let target_resource_type = sqlx::query!(
|
||||
"SELECT schema, description, format_extension, is_fileset
|
||||
"SELECT schema, description, format_extension
|
||||
FROM resource_type
|
||||
WHERE workspace_id = $1 AND name = $2",
|
||||
fork_workspace_id,
|
||||
@@ -5303,7 +5280,6 @@ async fn compare_two_resource_types(
|
||||
if source.schema != target.schema
|
||||
|| source.description != target.description
|
||||
|| source.format_extension != target.format_extension
|
||||
|| source.is_fileset != target.is_fileset
|
||||
{
|
||||
has_changes = true;
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ pub(crate) async fn change_workspace_id(
|
||||
// Archive old workspace: disable schedules, cancel remaining jobs, set deleted=true
|
||||
// Note: schedules were already moved to new workspace, so this will find 0 schedules
|
||||
info!("Archiving old workspace");
|
||||
let (_schedules_count, canceled_count, _deleted_tokens_count) =
|
||||
let (_schedules_count, canceled_count) =
|
||||
archive_workspace_impl(&db, &old_id, &authed.username).await?;
|
||||
|
||||
info!(
|
||||
|
||||
@@ -28049,7 +28049,6 @@ components:
|
||||
name: trigger_kind
|
||||
description: trigger kind (schedule, http, websocket...)
|
||||
in: query
|
||||
x-go-name: JobTriggerKindParam
|
||||
schema: *ref_160
|
||||
OrderDesc:
|
||||
name: order_desc
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.645.0
|
||||
version: 1.642.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -4091,21 +4091,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: path
|
||||
description: exact path match filter
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: description
|
||||
description: pattern match filter for description field (case-insensitive)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: value
|
||||
description: pattern match filter for non-secret variable values (case-insensitive)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
responses:
|
||||
@@ -5105,21 +5090,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: path
|
||||
description: exact path match filter
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: description
|
||||
description: pattern match filter for description field (case-insensitive)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: value
|
||||
description: JSONB subset match filter using base64 encoded JSON
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: resource list
|
||||
@@ -5244,19 +5214,10 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
responses:
|
||||
"200":
|
||||
description: map from resource type to file resource info
|
||||
description: map from resource type to file ext
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
format_extension:
|
||||
type: string
|
||||
nullable: true
|
||||
is_fileset:
|
||||
type: boolean
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/resources/type/delete/{path}:
|
||||
delete:
|
||||
@@ -8215,9 +8176,6 @@ paths:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -8263,9 +8221,6 @@ paths:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- value
|
||||
@@ -8577,9 +8532,6 @@ paths:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
responses:
|
||||
"200":
|
||||
description: app updated
|
||||
@@ -8619,9 +8571,6 @@ paths:
|
||||
type: string
|
||||
custom_path:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of value in the policy instead of overwriting it."
|
||||
js:
|
||||
type: string
|
||||
css:
|
||||
@@ -10680,16 +10629,6 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: resume_button_text
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: cancel_button_text
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Interactive slack approval message sent successfully
|
||||
@@ -10736,16 +10675,6 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: resume_button_text
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: cancel_button_text
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Interactive slack approval message sent successfully
|
||||
@@ -11191,7 +11120,7 @@ paths:
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- name: path
|
||||
description: filter by path (script path)
|
||||
description: filter by path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -11205,21 +11134,6 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: schedule_path
|
||||
description: exact match on the schedule's path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: description
|
||||
description: pattern match filter for description field (case-insensitive)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: summary
|
||||
description: pattern match filter for summary field (case-insensitive)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: schedule list
|
||||
@@ -16971,16 +16885,6 @@ paths:
|
||||
description: Filter by asset kinds (multiple values allowed)
|
||||
schema:
|
||||
type: string
|
||||
- name: path
|
||||
in: query
|
||||
description: exact path match filter
|
||||
schema:
|
||||
type: string
|
||||
- name: columns
|
||||
in: query
|
||||
description: JSONB subset match filter for columns using base64 encoded JSON
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: paginated assets in the workspace
|
||||
@@ -17354,11 +17258,10 @@ components:
|
||||
type: integer
|
||||
JobTriggerKind:
|
||||
name: trigger_kind
|
||||
description: "filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')"
|
||||
description: trigger kind (schedule, http, websocket...)
|
||||
in: query
|
||||
x-go-name: JobTriggerKindParam
|
||||
schema:
|
||||
type: string
|
||||
$ref: "#/components/schemas/JobTriggerKind"
|
||||
OrderDesc:
|
||||
name: order_desc
|
||||
description: order by desc order (default true)
|
||||
@@ -17367,19 +17270,19 @@ components:
|
||||
type: boolean
|
||||
CreatedBy:
|
||||
name: created_by
|
||||
description: "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
description: mask to filter exact matching user creator
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
Label:
|
||||
name: label
|
||||
description: "filter by exact matching job label. Supports comma-separated list (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release')"
|
||||
description: mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
Worker:
|
||||
name: worker
|
||||
description: "filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2')"
|
||||
description: worker this job was ran on
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -17445,7 +17348,7 @@ components:
|
||||
type: string
|
||||
ScriptStartPath:
|
||||
name: script_path_start
|
||||
description: "filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2')"
|
||||
description: mask to filter matching starting path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -17457,13 +17360,13 @@ components:
|
||||
type: string
|
||||
TriggerPath:
|
||||
name: trigger_path
|
||||
description: "filter by trigger path. Supports comma-separated list (e.g. 'f/trigger1,f/trigger2') and negation by prefixing all values with '!' (e.g. '!f/trigger1,!f/trigger2')"
|
||||
description: mask to filter by trigger path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
ScriptExactPath:
|
||||
name: script_path_exact
|
||||
description: "filter by exact matching script path. Supports comma-separated list (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2')"
|
||||
description: mask to filter exact matching path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -17578,7 +17481,7 @@ components:
|
||||
type: string
|
||||
Tag:
|
||||
name: tag
|
||||
description: "filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem')"
|
||||
description: filter on jobs with a given tag/worker group
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -17622,7 +17525,9 @@ components:
|
||||
enum: [Create, Update, Delete, Execute]
|
||||
JobKinds:
|
||||
name: job_kinds
|
||||
description: "filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies')"
|
||||
description:
|
||||
filter on job kind (values 'preview', 'script', 'dependencies', 'flow')
|
||||
separated by,
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -18529,9 +18434,6 @@ components:
|
||||
type: boolean
|
||||
on_behalf_of_email:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of_email value instead of overwriting it."
|
||||
assets:
|
||||
type: array
|
||||
items:
|
||||
@@ -19930,8 +19832,6 @@ components:
|
||||
format: date-time
|
||||
format_extension:
|
||||
type: string
|
||||
is_fileset:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
|
||||
@@ -19941,8 +19841,6 @@ components:
|
||||
schema: {}
|
||||
description:
|
||||
type: string
|
||||
is_fileset:
|
||||
type: boolean
|
||||
|
||||
Schedule:
|
||||
type: object
|
||||
@@ -20186,12 +20084,6 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- schedule
|
||||
@@ -20279,12 +20171,6 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- schedule
|
||||
- timezone
|
||||
@@ -20641,12 +20527,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -20733,12 +20613,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -20901,12 +20775,6 @@ components:
|
||||
retry:
|
||||
description: Retry configuration for failed executions
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -20969,12 +20837,6 @@ components:
|
||||
retry:
|
||||
description: Retry configuration for failed executions
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21144,12 +21006,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21204,12 +21060,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21346,12 +21196,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: "Retry configuration for failed executions."
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21514,12 +21358,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -21566,12 +21404,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- queue_url
|
||||
- aws_resource_path
|
||||
@@ -21730,12 +21562,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21778,12 +21604,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -21891,12 +21711,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -21950,12 +21764,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22057,12 +21865,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22112,12 +21914,6 @@ components:
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
description: Retry configuration for failed executions
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22166,12 +21962,6 @@ components:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
mode:
|
||||
$ref: "#/components/schemas/TriggerMode"
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
|
||||
required:
|
||||
- path
|
||||
@@ -22198,12 +21988,6 @@ components:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
email:
|
||||
type: string
|
||||
description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner.
|
||||
preserve_email:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
@@ -22623,9 +22407,6 @@ components:
|
||||
type: boolean
|
||||
on_behalf_of_email:
|
||||
type: string
|
||||
preserve_on_behalf_of:
|
||||
type: boolean
|
||||
description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original on_behalf_of_email value instead of overwriting it."
|
||||
required:
|
||||
- path
|
||||
|
||||
|
||||
@@ -663,30 +663,12 @@ async fn global_proxy(
|
||||
|
||||
let base_url = provider.get_base_url(None, &db).await?;
|
||||
|
||||
let is_anthropic = provider.is_anthropic();
|
||||
let is_anthropic_sdk = headers.get("X-Anthropic-SDK").is_some();
|
||||
|
||||
let url = if is_anthropic_sdk {
|
||||
let truncated_base_url = base_url.trim_end_matches("/v1");
|
||||
format!("{}/{}", truncated_base_url, ai_path)
|
||||
} else {
|
||||
format!("{}/{}", base_url, ai_path)
|
||||
};
|
||||
let url = format!("{}/{}", base_url, ai_path);
|
||||
|
||||
let mut request = HTTP_CLIENT
|
||||
.request(method, url)
|
||||
.header("content-type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", &api_key));
|
||||
|
||||
if is_anthropic {
|
||||
request = request.header("X-API-Key", &api_key);
|
||||
}
|
||||
|
||||
for (header_name, header_value) in headers.iter() {
|
||||
if header_name.to_string().starts_with("anthropic-") {
|
||||
request = request.header(header_name, header_value);
|
||||
}
|
||||
}
|
||||
.header("Authorization", format!("Bearer {}", api_key));
|
||||
|
||||
// Apply custom headers from AI_HTTP_HEADERS environment variable
|
||||
for (header_name, header_value) in AI_HTTP_HEADERS.iter() {
|
||||
|
||||
@@ -83,12 +83,6 @@ pub struct QueryDynamicEnumJson {
|
||||
pub dynamic_enums_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryButtonText {
|
||||
pub resume_button_text: Option<String>,
|
||||
pub cancel_button_text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApprovalFormDetails {
|
||||
pub message_str: String,
|
||||
@@ -272,12 +266,7 @@ pub async fn get_approval_form_details(
|
||||
})
|
||||
});
|
||||
|
||||
let args_str = args.map_or("None".to_string(), |a| {
|
||||
serde_json::from_str::<serde_json::Value>(a.get())
|
||||
.ok()
|
||||
.and_then(|v| serde_json::to_string_pretty(&v).ok())
|
||||
.unwrap_or_else(|| a.get().to_string())
|
||||
});
|
||||
let args_str = args.map_or("None".to_string(), |a| a.get().to_string());
|
||||
let parent_job_id_str = parent_job_id.map_or("None".to_string(), |id| id.to_string());
|
||||
let script_path_str = script_path.as_deref().unwrap_or("None");
|
||||
|
||||
@@ -293,7 +282,7 @@ pub async fn get_approval_form_details(
|
||||
{}: {created_by}\n\n\
|
||||
{}: {created_at_formatted}\n\n\
|
||||
{}: {script_path_str}\n\n\
|
||||
{}:\n```\n{args_str}\n```\n\n\
|
||||
{}: {args_str}\n\n\
|
||||
{}: {parent_job_id_str}\n\n",
|
||||
bold_format.replace("{}", "Created by"),
|
||||
bold_format.replace("{}", "Created at"),
|
||||
|
||||
@@ -39,6 +39,8 @@ use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
#[cfg(feature = "parquet")]
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, value::RawValue};
|
||||
@@ -65,8 +67,6 @@ use windmill_common::{
|
||||
workspaces::{check_user_against_rule, ProtectionRuleKind, RuleCheckResult},
|
||||
HUB_BASE_URL,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
use windmill_store::resources::get_resource_value_interpolated_internal;
|
||||
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
@@ -75,7 +75,11 @@ use windmill_queue::{push, PushArgs, PushArgsOwned, PushIsolationLevel};
|
||||
#[cfg(feature = "parquet")]
|
||||
use hmac::Mac;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::{jwt, oauth2::HmacSha256, variables::get_workspace_key};
|
||||
use windmill_common::{
|
||||
jwt,
|
||||
oauth2::HmacSha256,
|
||||
variables::get_workspace_key,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_types::s3::{S3Object, S3Permission};
|
||||
|
||||
@@ -275,7 +279,6 @@ pub struct CreateApp {
|
||||
pub draft_only: Option<bool>,
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
pub preserve_on_behalf_of: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -286,7 +289,6 @@ pub struct EditApp {
|
||||
pub policy: Option<Policy>,
|
||||
pub deployment_message: Option<String>,
|
||||
pub custom_path: Option<String>,
|
||||
pub preserve_on_behalf_of: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -441,9 +443,7 @@ async fn get_raw_app_data(
|
||||
if let Some(os) = object_store {
|
||||
let path = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
let stream = os
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(
|
||||
path,
|
||||
))
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(path))
|
||||
.await
|
||||
.map_err(windmill_object_store::object_store_error_to_error)?
|
||||
.bytes()
|
||||
@@ -958,10 +958,7 @@ async fn store_raw_app_file<'a>(
|
||||
|
||||
if let Some(os) = object_store {
|
||||
if let Err(e) = os
|
||||
.put(
|
||||
&windmill_object_store::object_store_reexports::Path::from(path.clone()),
|
||||
data.into(),
|
||||
)
|
||||
.put(&windmill_object_store::object_store_reexports::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
@@ -1181,14 +1178,8 @@ async fn create_app_internal<'a>(
|
||||
}
|
||||
}
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let should_preserve = app.preserve_on_behalf_of.unwrap_or(false)
|
||||
&& windmill_common::can_preserve_on_behalf_of(&authed)
|
||||
&& app.policy.on_behalf_of.is_some();
|
||||
|
||||
if !should_preserve {
|
||||
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
app.policy.on_behalf_of_email = Some(authed.email.clone());
|
||||
}
|
||||
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
app.policy.on_behalf_of_email = Some(authed.email.clone());
|
||||
let path = app.path.clone();
|
||||
if &app.path == "" {
|
||||
return Err(Error::BadRequest("App path cannot be empty".to_string()));
|
||||
@@ -1279,22 +1270,6 @@ async fn create_app_internal<'a>(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
if should_preserve {
|
||||
if let Some(ref obo_email) = app.policy.on_behalf_of_email {
|
||||
if obo_email != &authed.email {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"apps.on_behalf_of",
|
||||
ActionKind::Create,
|
||||
w_id,
|
||||
Some(&app.path),
|
||||
Some([("on_behalf_of", obo_email.as_str()), ("action", "create")].into()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
|
||||
if let Some(dm) = &app.deployment_message {
|
||||
args.insert("deployment_message".to_string(), to_raw_value(&dm));
|
||||
@@ -1624,7 +1599,6 @@ async fn update_app_internal<'a>(
|
||||
use sql_builder::prelude::*;
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let mut preserved_on_behalf_of: Option<String> = None;
|
||||
let npath = if ns.policy.is_some()
|
||||
|| ns.path.is_some()
|
||||
|| ns.summary.is_some()
|
||||
@@ -1690,20 +1664,8 @@ async fn update_app_internal<'a>(
|
||||
}
|
||||
|
||||
if let Some(mut npolicy) = ns.policy {
|
||||
let should_preserve = ns.preserve_on_behalf_of.unwrap_or(false)
|
||||
&& windmill_common::can_preserve_on_behalf_of(&authed)
|
||||
&& npolicy.on_behalf_of.is_some();
|
||||
|
||||
if should_preserve {
|
||||
if let Some(ref obo_email) = npolicy.on_behalf_of_email {
|
||||
if obo_email != &authed.email {
|
||||
preserved_on_behalf_of = Some(obo_email.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
npolicy.on_behalf_of_email = Some(authed.email.clone());
|
||||
}
|
||||
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
npolicy.on_behalf_of_email = Some(authed.email.clone());
|
||||
sqlb.set(
|
||||
"policy",
|
||||
quote(serde_json::to_string(&json!(npolicy)).map_err(|e| {
|
||||
@@ -1785,24 +1747,6 @@ async fn update_app_internal<'a>(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
if let Some(on_behalf_of) = preserved_on_behalf_of {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed,
|
||||
"apps.on_behalf_of",
|
||||
ActionKind::Update,
|
||||
w_id,
|
||||
Some(&npath),
|
||||
Some(
|
||||
[
|
||||
("on_behalf_of", on_behalf_of.as_str()),
|
||||
("action", "update"),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let mut args: HashMap<String, Box<serde_json::value::RawValue>> = HashMap::new();
|
||||
if let Some(dm) = ns.deployment_message {
|
||||
|
||||
@@ -15,10 +15,7 @@ use sqlx::{
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
pub use windmill_common::db::DB;
|
||||
use windmill_common::{
|
||||
error::Error,
|
||||
utils::{generate_lock_id, GIT_VERSION},
|
||||
};
|
||||
use windmill_common::{error::Error, utils::{generate_lock_id, GIT_VERSION}};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use windmill_api_auth::{ApiAuthed, OptJobAuthed};
|
||||
@@ -78,9 +75,6 @@ lazy_static::lazy_static! {
|
||||
(20260207000004, include_str!(
|
||||
"../../migrations/20260207000004_concurrent_indexes_other.up.sql"
|
||||
).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY").replace("DROP INDEX", "DROP INDEX CONCURRENTLY")),
|
||||
(20260225100000, include_str!(
|
||||
"../../migrations/20260225100000_asset_covering_index.up.sql"
|
||||
).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY").replace("DROP INDEX", "DROP INDEX CONCURRENTLY")),
|
||||
].into_iter().collect();
|
||||
}
|
||||
|
||||
@@ -261,7 +255,8 @@ pub async fn migrate(
|
||||
if let Err(err) = sqlx::query!(
|
||||
"DELETE FROM _sqlx_migrations WHERE
|
||||
version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR
|
||||
version=20250201145631 OR version=20250201145632 OR version=20251006143821"
|
||||
version=20250201145631 OR version=20250201145632 OR version=20251006143821 OR
|
||||
version=20260207000001 OR version=20260207000002 OR version=20260207000003 OR version=20260207000004"
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
@@ -269,31 +264,6 @@ pub async fn migrate(
|
||||
tracing::info!("Could not remove sqlx migrations: {err:#}");
|
||||
}
|
||||
|
||||
// For migrations that were replaced (same version, new content), only delete if
|
||||
// the stored checksum doesn't match the current file — i.e., it's a stale record
|
||||
// from the old broken version. Once the new migration is applied, the checksum
|
||||
// matches and the record is kept, avoiding expensive re-application on every start.
|
||||
let migrator = sqlx::migrate!("../migrations");
|
||||
let potentially_stale: &[i64] = &[
|
||||
20260207000001,
|
||||
20260207000002,
|
||||
20260207000003,
|
||||
20260207000004,
|
||||
];
|
||||
for m in migrator.migrations.iter() {
|
||||
if potentially_stale.contains(&m.version) {
|
||||
if let Err(err) =
|
||||
sqlx::query("DELETE FROM _sqlx_migrations WHERE version = $1 AND checksum != $2")
|
||||
.bind(m.version)
|
||||
.bind(&*m.checksum)
|
||||
.execute(db)
|
||||
.await
|
||||
{
|
||||
tracing::info!("Could not clean up stale migration {}: {err:#}", m.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
_ = killpill_rx.recv() => {
|
||||
tracing::info!("Killpill received, stopping migration");
|
||||
@@ -339,11 +309,12 @@ pub async fn wait_for_migrations(
|
||||
let mut attempts = 0;
|
||||
|
||||
loop {
|
||||
let is_applied: Result<Option<bool>, sqlx::Error> =
|
||||
sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM _sqlx_migrations WHERE version = $1)")
|
||||
.bind(latest_version)
|
||||
.fetch_one(db)
|
||||
.await;
|
||||
let is_applied: Result<Option<bool>, sqlx::Error> = sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM _sqlx_migrations WHERE version = $1)",
|
||||
)
|
||||
.bind(latest_version)
|
||||
.fetch_one(db)
|
||||
.await;
|
||||
|
||||
match is_applied {
|
||||
Ok(Some(true)) => {
|
||||
|
||||
@@ -1933,7 +1933,7 @@ async fn count_completed_jobs_detail(
|
||||
|
||||
if let Some(after_s_ago) = query.completed_after_s_ago {
|
||||
let after = Utc::now() - chrono::Duration::seconds(after_s_ago);
|
||||
sqlb.and_where_gt("completed_at", "?".bind(&after.to_rfc3339()));
|
||||
sqlb.and_where_gt("ended_at", "?".bind(&after.to_rfc3339()));
|
||||
}
|
||||
|
||||
if let Some(success) = query.success {
|
||||
|
||||
@@ -387,11 +387,10 @@ async fn handle_authorization_code_grant(
|
||||
let token_family = sqlx::types::Uuid::new_v4();
|
||||
let scopes = auth_code.scopes;
|
||||
|
||||
// Create access token (rejects archived workspaces inline)
|
||||
let rows = sqlx::query!(
|
||||
// Create access token
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO token (token, email, label, expiration, scopes, workspace_id)
|
||||
SELECT $1::varchar, $2::varchar, $3::varchar, now() + ($4 || ' seconds')::interval, $5::text[], $6::varchar
|
||||
WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $6 AND deleted = true)",
|
||||
VALUES ($1, $2, $3, now() + ($4 || ' seconds')::interval, $5, $6)",
|
||||
access_token,
|
||||
auth_code.user_email,
|
||||
format!("mcp-oauth-{}", auth_code.client_id),
|
||||
@@ -401,13 +400,10 @@ async fn handle_authorization_code_grant(
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
{
|
||||
tracing::error!("Failed to create access token: {}", e);
|
||||
OAuthTokenError::server_error("Failed to create access token")
|
||||
})?;
|
||||
if rows.rows_affected() == 0 {
|
||||
return Err(OAuthTokenError::invalid_grant(
|
||||
"Cannot create a token for an archived workspace",
|
||||
return Err(OAuthTokenError::server_error(
|
||||
"Failed to create access token",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -518,11 +514,10 @@ async fn handle_refresh_token_grant(
|
||||
let new_refresh_token = rd_string(32);
|
||||
let scopes = token_row.scopes;
|
||||
|
||||
// Create new access token (rejects archived workspaces inline)
|
||||
let rows = sqlx::query!(
|
||||
// Create new access token
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO token (token, email, label, expiration, scopes, workspace_id)
|
||||
SELECT $1::varchar, $2::varchar, $3::varchar, now() + ($4 || ' seconds')::interval, $5::text[], $6::varchar
|
||||
WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $6 AND deleted = true)",
|
||||
VALUES ($1, $2, $3, now() + ($4 || ' seconds')::interval, $5, $6)",
|
||||
new_access_token,
|
||||
token_row.user_email,
|
||||
format!("mcp-oauth-{}", token_row.client_id),
|
||||
@@ -532,13 +527,10 @@ async fn handle_refresh_token_grant(
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
{
|
||||
tracing::error!("Failed to create new access token: {}", e);
|
||||
OAuthTokenError::server_error("Failed to create access token")
|
||||
})?;
|
||||
if rows.rows_affected() == 0 {
|
||||
return Err(OAuthTokenError::invalid_grant(
|
||||
"Cannot create a token for an archived workspace",
|
||||
return Err(OAuthTokenError::server_error(
|
||||
"Failed to create access token",
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ use crate::jobs::{QueryApprover, ResumeUrls};
|
||||
use crate::{
|
||||
approvals::{
|
||||
extract_w_id_from_resume_url, handle_resume_action, ApprovalFormDetails, FieldType,
|
||||
MessageFormat, QueryButtonText, QueryDefaultArgsJson, QueryDynamicEnumJson,
|
||||
QueryFlowStepId, QueryMessage, ResumeFormField, ResumeSchema,
|
||||
MessageFormat, QueryDefaultArgsJson, QueryDynamicEnumJson, QueryFlowStepId, QueryMessage,
|
||||
ResumeFormField, ResumeSchema,
|
||||
},
|
||||
auth::OptTokened,
|
||||
};
|
||||
@@ -107,8 +107,6 @@ struct ModalActionValue {
|
||||
flow_step_id: Option<String>,
|
||||
default_args_json: Option<String>,
|
||||
dynamic_enums_json: Option<String>,
|
||||
resume_button_text: Option<String>,
|
||||
cancel_button_text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -202,8 +200,6 @@ pub async fn slack_app_callback_handler(
|
||||
container,
|
||||
default_args_json.as_ref(),
|
||||
dynamic_enums_json.as_ref(),
|
||||
parsed_value.resume_button_text.as_deref(),
|
||||
parsed_value.cancel_button_text.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
@@ -233,7 +229,6 @@ pub async fn request_slack_approval(
|
||||
Query(flow_step_id): Query<QueryFlowStepId>,
|
||||
Query(default_args_json): Query<QueryDefaultArgsJson>,
|
||||
Query(dynamic_enums_json): Query<QueryDynamicEnumJson>,
|
||||
Query(button_text): Query<QueryButtonText>,
|
||||
) -> Result<StatusCode, Error> {
|
||||
let slack_resource_path = slack_resource_path.slack_resource_path;
|
||||
let channel_id = channel_id.channel_id;
|
||||
@@ -260,8 +255,6 @@ pub async fn request_slack_approval(
|
||||
flow_step_id.as_str(),
|
||||
default_args_json.default_args_json.as_ref(),
|
||||
dynamic_enums_json.dynamic_enums_json.as_ref(),
|
||||
button_text.resume_button_text.as_deref(),
|
||||
button_text.cancel_button_text.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
@@ -759,8 +752,6 @@ async fn send_slack_message(
|
||||
flow_step_id: &str,
|
||||
default_args_json: Option<&serde_json::Value>,
|
||||
dynamic_enums_json: Option<&serde_json::Value>,
|
||||
resume_button_text: Option<&str>,
|
||||
cancel_button_text: Option<&str>,
|
||||
) -> Result<StatusCode, Box<dyn std::error::Error>> {
|
||||
let url = "https://slack.com/api/chat.postMessage";
|
||||
|
||||
@@ -788,14 +779,6 @@ async fn send_slack_message(
|
||||
value["dynamic_enums_json"] = dynamic_enums_json.clone();
|
||||
}
|
||||
|
||||
if let Some(resume_button_text) = resume_button_text {
|
||||
value["resume_button_text"] = serde_json::json!(resume_button_text);
|
||||
}
|
||||
|
||||
if let Some(cancel_button_text) = cancel_button_text {
|
||||
value["cancel_button_text"] = serde_json::json!(cancel_button_text);
|
||||
}
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"channel": channel_id,
|
||||
"text": "A flow has been suspended. Please approve or reject the flow.",
|
||||
@@ -859,8 +842,6 @@ async fn get_modal_blocks(
|
||||
container: Container,
|
||||
default_args_json: Option<&serde_json::Value>,
|
||||
dynamic_enums_json: Option<&serde_json::Value>,
|
||||
resume_button_text: Option<&str>,
|
||||
cancel_button_text: Option<&str>,
|
||||
) -> Result<axum::Json<serde_json::Value>, Error> {
|
||||
let approval_details = crate::approvals::get_approval_form_details(
|
||||
db,
|
||||
@@ -914,8 +895,6 @@ async fn get_modal_blocks(
|
||||
&urls.resume,
|
||||
resource_path,
|
||||
container,
|
||||
resume_button_text,
|
||||
cancel_button_text,
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -926,8 +905,6 @@ fn construct_payload(
|
||||
resume_url: &str,
|
||||
resource_path: &str,
|
||||
container: Container,
|
||||
resume_button_text: Option<&str>,
|
||||
cancel_button_text: Option<&str>,
|
||||
) -> serde_json::Value {
|
||||
let mut view = serde_json::json!({
|
||||
"type": "modal",
|
||||
@@ -935,12 +912,12 @@ fn construct_payload(
|
||||
"notify_on_close": true,
|
||||
"title": {
|
||||
"type": "plain_text",
|
||||
"text": "Workflow Suspended"
|
||||
"text": "Worfklow Suspended"
|
||||
},
|
||||
"blocks": blocks,
|
||||
"submit": {
|
||||
"type": "plain_text",
|
||||
"text": resume_button_text.unwrap_or("Resume Workflow")
|
||||
"text": "Resume Workflow"
|
||||
},
|
||||
"private_metadata": serde_json::json!({ "resume_url": resume_url, "resource_path": resource_path, "container": container, "hide_cancel": hide_cancel }).to_string(),
|
||||
});
|
||||
@@ -948,7 +925,7 @@ fn construct_payload(
|
||||
if !hide_cancel {
|
||||
view["close"] = serde_json::json!({
|
||||
"type": "plain_text",
|
||||
"text": cancel_button_text.unwrap_or("Cancel Workflow")
|
||||
"text": "Cancel Workflow"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -972,8 +949,6 @@ async fn open_modal_with_blocks(
|
||||
container: Container,
|
||||
default_args_json: Option<&serde_json::Value>,
|
||||
dynamic_enums_json: Option<&serde_json::Value>,
|
||||
resume_button_text: Option<&str>,
|
||||
cancel_button_text: Option<&str>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let resume_id = rand::random::<u32>();
|
||||
let blocks_json = match get_modal_blocks(
|
||||
@@ -989,8 +964,6 @@ async fn open_modal_with_blocks(
|
||||
container,
|
||||
default_args_json,
|
||||
dynamic_enums_json,
|
||||
resume_button_text,
|
||||
cancel_button_text,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -179,12 +179,13 @@ pub async fn benchmark_verify(benchmark_jobs: i32, db: &DB) {
|
||||
let canceled = row.canceled.unwrap_or(0);
|
||||
let total = succeeded + failed + canceled;
|
||||
|
||||
let remaining_in_queue =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue WHERE workspace_id = 'admins'",)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.expect("benchmark verify queue query failed")
|
||||
.unwrap_or(0);
|
||||
let remaining_in_queue = sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM v2_job_queue WHERE workspace_id = 'admins'",
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.expect("benchmark verify queue query failed")
|
||||
.unwrap_or(0);
|
||||
|
||||
println!("=== BENCHMARK VERIFICATION ===");
|
||||
println!(" kind: {benchmark_kind}");
|
||||
@@ -247,12 +248,10 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("failed to clean up job_perms: {e:#}"));
|
||||
sqlx::query!(
|
||||
"DELETE FROM concurrency_key WHERE key LIKE 'bench_%' OR key LIKE 'u/admin/bench_%'"
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("failed to clean up concurrency_key: {e:#}"));
|
||||
sqlx::query!("DELETE FROM concurrency_key WHERE key LIKE 'bench_%' OR key LIKE 'u/admin/bench_%'")
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("failed to clean up concurrency_key: {e:#}"));
|
||||
sqlx::query!("DELETE FROM concurrency_counter WHERE concurrency_id LIKE 'bench_%' OR concurrency_id LIKE 'u/admin/bench_%'")
|
||||
.execute(db)
|
||||
.await
|
||||
@@ -638,13 +637,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed noop queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&noop_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &noop_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed noop runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed noop runtime"));
|
||||
|
||||
// 2) sequentialflow jobs
|
||||
if portion > 0 {
|
||||
@@ -666,13 +661,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&sf_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sf_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow runtime"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
|
||||
&sf_uuids,
|
||||
@@ -702,13 +693,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&sl_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sl_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs runtime"));
|
||||
}
|
||||
|
||||
// 4) concurrencylimit jobs
|
||||
@@ -733,13 +720,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &cl_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&cl_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &cl_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit runtime"));
|
||||
let cl_concurrency_id = "u/admin/bench_conclimit";
|
||||
sqlx::query!(
|
||||
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
|
||||
@@ -780,13 +763,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &ck_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&ck_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &ck_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey runtime"));
|
||||
let ck_concurrency_id = "bench_shared_concurrency_key";
|
||||
sqlx::query!(
|
||||
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
|
||||
@@ -828,13 +807,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&noop_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &noop_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop runtime"));
|
||||
|
||||
// 2) sequentialflow jobs
|
||||
if portion > 0 {
|
||||
@@ -856,15 +831,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&sf_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sf_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| {
|
||||
panic!("failed to insert mixed_no_cc sequentialflow runtime")
|
||||
});
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow runtime"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
|
||||
&sf_uuids,
|
||||
@@ -894,13 +863,9 @@ pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
|
||||
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno")
|
||||
.execute(&mut *tx)
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs queue"));
|
||||
sqlx::query!(
|
||||
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
&sl_uuids
|
||||
)
|
||||
sqlx::query!("INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", &sl_uuids)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs runtime"));
|
||||
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs runtime"));
|
||||
}
|
||||
}
|
||||
"none" => {}
|
||||
|
||||
@@ -109,50 +109,6 @@ pub const DEFAULT_MAX_CONNECTIONS_INDEXER: u32 = 5;
|
||||
pub const DEFAULT_HUB_BASE_URL: &str = "https://hub.windmill.dev";
|
||||
pub const PRIVATE_HUB_MIN_VERSION: i32 = 10_000_000;
|
||||
pub const SERVICE_LOG_RETENTION_SECS: i64 = 60 * 60 * 24 * 14; // 2 weeks retention period for logs
|
||||
pub const WM_DEPLOYERS_GROUP: &str = "wm_deployers";
|
||||
|
||||
/// Checks if the user is allowed to preserve on_behalf_of values (admin or deployer).
|
||||
pub fn can_preserve_on_behalf_of(authed: &impl db::Authable) -> bool {
|
||||
authed.is_admin() || authed.groups().iter().any(|g| g == &WM_DEPLOYERS_GROUP)
|
||||
}
|
||||
|
||||
/// Checks if on-behalf-of preservation actually happened (the target user differs from the acting user).
|
||||
/// Returns Some(target_identifier) if preservation occurred, None otherwise.
|
||||
pub fn check_on_behalf_of_preservation(
|
||||
on_behalf_of_identifier: Option<&str>,
|
||||
preserve: bool,
|
||||
authed: &impl db::Authable,
|
||||
authed_identifier: &str,
|
||||
) -> Option<String> {
|
||||
if preserve && can_preserve_on_behalf_of(authed) {
|
||||
if let Some(id) = on_behalf_of_identifier {
|
||||
if id != authed_identifier {
|
||||
return Some(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Determines the on_behalf_of_email value to use when creating/updating a flow or script.
|
||||
/// - If `on_behalf_of_email` is None, returns None
|
||||
/// - If `preserve` is true and the user is admin or in the deployers group, returns the original value
|
||||
/// - Otherwise, returns the authenticated user's email
|
||||
pub fn resolve_on_behalf_of_email<'a>(
|
||||
on_behalf_of_email: Option<&'a str>,
|
||||
preserve: bool,
|
||||
authed: &'a impl db::Authable,
|
||||
) -> Option<&'a str> {
|
||||
if on_behalf_of_email.is_some() {
|
||||
if preserve && can_preserve_on_behalf_of(authed) {
|
||||
on_behalf_of_email
|
||||
} else {
|
||||
Some(authed.email())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! add_time {
|
||||
|
||||
@@ -52,10 +52,7 @@ fn extract_assets_from_raw_value(
|
||||
if prefix {
|
||||
let s = serde_json::from_str::<String>(value.get()).ok()?;
|
||||
let (kind, path) = parse_asset_syntax(&s, false)?;
|
||||
assets.push(RuntimeAsset {
|
||||
path: path.to_string(),
|
||||
kind: crate::assets::asset_kind_from_parser(kind),
|
||||
});
|
||||
assets.push(RuntimeAsset { path: path.to_string(), kind: crate::assets::asset_kind_from_parser(kind) });
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@@ -49,7 +49,9 @@ pub fn extract_workspace_dependencies_annotated_refs(
|
||||
Some(&RE_PYTHON),
|
||||
runnable_path,
|
||||
),
|
||||
Go => WorkspaceDependenciesAnnotatedRefs::parse("//", "go_mod", code, None, runnable_path),
|
||||
Go => {
|
||||
WorkspaceDependenciesAnnotatedRefs::parse("//", "go_mod", code, None, runnable_path)
|
||||
}
|
||||
Php => WorkspaceDependenciesAnnotatedRefs::parse(
|
||||
"//",
|
||||
"composer_json",
|
||||
@@ -65,8 +67,11 @@ pub async fn prefetch_cached_script(
|
||||
script: Script<ScriptRunnableSettingsHandle>,
|
||||
db: &DB,
|
||||
) -> crate::error::Result<Script<ScriptRunnableSettingsInline>> {
|
||||
let rs = runnable_settings::from_handle(script.runnable_settings.runnable_settings_handle, db)
|
||||
.await?;
|
||||
let rs = runnable_settings::from_handle(
|
||||
script.runnable_settings.runnable_settings_handle,
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
let (debouncing_settings, concurrency_settings) =
|
||||
runnable_settings::prefetch_cached(&rs, db).await?;
|
||||
|
||||
@@ -374,8 +379,11 @@ pub async fn clone_script<'c>(
|
||||
)));
|
||||
};
|
||||
|
||||
let rs =
|
||||
runnable_settings::from_handle(s.runnable_settings.runnable_settings_handle, db).await?;
|
||||
let rs = runnable_settings::from_handle(
|
||||
s.runnable_settings.runnable_settings_handle,
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
let (debouncing_settings, concurrency_settings) =
|
||||
runnable_settings::prefetch_cached(&rs, db).await?;
|
||||
|
||||
@@ -416,7 +424,6 @@ pub async fn clone_script<'c>(
|
||||
codebase: s.codebase,
|
||||
has_preprocessor: s.has_preprocessor,
|
||||
on_behalf_of_email: s.on_behalf_of_email,
|
||||
preserve_on_behalf_of: None,
|
||||
assets: s.assets,
|
||||
};
|
||||
|
||||
|
||||
@@ -384,11 +384,8 @@ impl WorkspaceDependenciesPrefetched {
|
||||
|
||||
Box::pin(async {
|
||||
let r = if let Some(wdar) =
|
||||
crate::scripts::extract_workspace_dependencies_annotated_refs(
|
||||
&language,
|
||||
code,
|
||||
runnable_path,
|
||||
) {
|
||||
crate::scripts::extract_workspace_dependencies_annotated_refs(&language, code, runnable_path)
|
||||
{
|
||||
tracing::debug!(workspace_id, ?language, "found explicit annotations");
|
||||
|
||||
let expanded = wdar
|
||||
|
||||
1469
backend/windmill-duckdb-ffi-internal/Cargo.lock
generated
1469
backend/windmill-duckdb-ffi-internal/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user