Compare commits

..

2 Commits

Author SHA1 Message Date
Ruben Fiszel
79780ff8ac pg_embed 2026-01-09 12:50:08 +00:00
Ruben Fiszel
da8f8e11b2 pg_embed 2026-01-09 12:43:03 +00:00
48 changed files with 885 additions and 1797 deletions

View File

@@ -1,30 +0,0 @@
#!/bin/bash
# Resolve _ee.rs symlinks to actual files so Claude can read them
# This script runs before each user prompt is processed
set -e
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
# Find all _ee.rs symlinks and store their targets
find "$PROJECT_DIR" -name "*_ee.rs" -type l 2>/dev/null | while read -r symlink; do
target=$(readlink -f "$symlink" 2>/dev/null) || continue
# Only process if target file exists
if [[ -f "$target" ]]; then
# Store symlink path and target in manifest
echo "$symlink|$target" >> "$MANIFEST_FILE.tmp"
# Replace symlink with actual file content
rm "$symlink"
cp "$target" "$symlink"
fi
done
# Atomically replace manifest
if [[ -f "$MANIFEST_FILE.tmp" ]]; then
mv "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
fi
exit 0

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# Restore _ee.rs symlinks after Claude finishes processing
# This script runs when Claude stops
# IMPORTANT: Copies any modifications back to the target before restoring symlinks
set -e
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
# Check if manifest exists
if [[ ! -f "$MANIFEST_FILE" ]]; then
exit 0
fi
# Read manifest and restore symlinks
while IFS='|' read -r symlink target; do
if [[ -n "$symlink" && -n "$target" ]]; then
# If the file exists (not a symlink) and target exists, copy changes back
if [[ -f "$symlink" && ! -L "$symlink" && -e "$target" ]]; then
# Copy the potentially modified file back to the target
cp "$symlink" "$target"
fi
# Remove the regular file (which was a copy)
rm -f "$symlink" 2>/dev/null || true
# Recreate the symlink
ln -s "$target" "$symlink" 2>/dev/null || true
fi
done < "$MANIFEST_FILE"
# Clean up manifest
rm -f "$MANIFEST_FILE"
exit 0

View File

@@ -1,41 +1,7 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/resolve-symlinks.sh",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
"timeout": 30
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
"timeout": 30
}
]
}
]
},
"permissions": {
"allow": [
"Read(**/*.rs)",
"Bash(ls:*)",
"Bash(grep:*)",
"Bash(cat:*)",
@@ -90,11 +56,10 @@
"Bash(git checkout:*)",
"Bash(git merge:*)",
"Bash(git rebase:*)"
]
],
"additionalDirectories": [
"../windmill-ee-private/"
]
},
"enableAllProjectMcpServers": true,
"enabledPlugins": {
"rust-analyzer-lsp@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true
}
"enableAllProjectMcpServers": true
}

View File

@@ -49,9 +49,9 @@ jobs:
needs.check-membership.outputs.is_member == 'true'
runs-on: ubicloud-standard-8
permissions:
contents: write
contents: read
pull-requests: write
issues: write
issues: read
id-token: write
steps:
- name: Checkout repository
@@ -63,15 +63,16 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
model: claude-opus-4-1-20250805
fallback_model: claude-sonnet-4-20250514
timeout_minutes: "60"
allowed_tools: "mcp__github__create_pull_request"
allowed_bots: "windmill-internal-app[bot]"
custom_instructions: |
## IMPORTANT INSTRUCTIONS
- Your branch name should be a short description of the requested changes.
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main.
## Available Tools
- mcp__github__create_pull_request: Create PRs from branches
trigger_phrase: "/ai-fast"
plugins: "rust-analyzer-lsp@claude-plugins-official,typescript-lsp@claude-plugins-official"
settings: |
{
"env": {
"SQLX_OFFLINE": "true"
}
}
claude_args: |
--allowedTools "Bash,WebFetch,WebSearch"
--model opus

View File

@@ -50,9 +50,9 @@ jobs:
runs-on: ubicloud-standard-8
timeout-minutes: 60
permissions:
contents: write
contents: read
pull-requests: write
issues: write
issues: read
id-token: write
steps:
- name: Checkout repository
@@ -95,9 +95,8 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: "windmill-internal-app[bot]"
trigger_phrase: "/ai"
plugins: "rust-analyzer-lsp@claude-plugins-official,typescript-lsp@claude-plugins-official"
allowed_bots: 'windmill-internal-app[bot]'
trigger_phrase: '/ai'
settings: |
{
"env": {

View File

@@ -1,20 +1,5 @@
# Changelog
## [1.603.2](https://github.com/windmill-labs/windmill/compare/v1.603.1...v1.603.2) (2026-01-09)
### Bug Fixes
* windmill ee full cache permission issues for non root users ([#7536](https://github.com/windmill-labs/windmill/issues/7536)) ([35ddfc4](https://github.com/windmill-labs/windmill/commit/35ddfc428dc98e492012731f60feda64ff5ebc2c))
## [1.603.1](https://github.com/windmill-labs/windmill/compare/v1.603.0...v1.603.1) (2026-01-09)
### Bug Fixes
* Better workspace storage settings ([#7533](https://github.com/windmill-labs/windmill/issues/7533)) ([17d29cd](https://github.com/windmill-labs/windmill/commit/17d29cd8c770fbe1f7503367474951e5eb6991b1))
* Fix custom instance user migration ([#7534](https://github.com/windmill-labs/windmill/issues/7534)) ([7b19ca4](https://github.com/windmill-labs/windmill/commit/7b19ca44a3ff7e2e87d5a358873370aeb40dc7a3))
## [1.603.0](https://github.com/windmill-labs/windmill/compare/v1.602.0...v1.603.0) (2026-01-09)

325
backend/Cargo.lock generated
View File

@@ -1268,7 +1268,7 @@ dependencies = [
"hyper 1.8.1",
"hyper-util",
"itoa",
"matchit",
"matchit 0.7.3",
"memchr",
"mime",
"multer",
@@ -2823,7 +2823,7 @@ dependencies = [
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
"parking_lot_core 0.9.12",
]
[[package]]
@@ -2837,7 +2837,7 @@ dependencies = [
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
"parking_lot_core 0.9.12",
]
[[package]]
@@ -2894,7 +2894,7 @@ dependencies = [
"itertools 0.14.0",
"log",
"object_store",
"parking_lot",
"parking_lot 0.12.5",
"parquet",
"rand 0.8.5",
"regex",
@@ -2929,7 +2929,7 @@ dependencies = [
"itertools 0.14.0",
"log",
"object_store",
"parking_lot",
"parking_lot 0.12.5",
"tokio",
]
@@ -3102,7 +3102,7 @@ dependencies = [
"itertools 0.14.0",
"log",
"object_store",
"parking_lot",
"parking_lot 0.12.5",
"parquet",
"rand 0.8.5",
"tokio",
@@ -3127,7 +3127,7 @@ dependencies = [
"futures",
"log",
"object_store",
"parking_lot",
"parking_lot 0.12.5",
"rand 0.8.5",
"tempfile",
"url",
@@ -3263,7 +3263,7 @@ dependencies = [
"datafusion-common",
"datafusion-expr",
"datafusion-physical-plan",
"parking_lot",
"parking_lot 0.12.5",
"paste",
]
@@ -3404,7 +3404,7 @@ dependencies = [
"indexmap 2.11.1",
"itertools 0.14.0",
"log",
"parking_lot",
"parking_lot 0.12.5",
"pin-project-lite",
"tokio",
]
@@ -3429,7 +3429,7 @@ dependencies = [
"itertools 0.14.0",
"log",
"object_store",
"parking_lot",
"parking_lot 0.12.5",
"tokio",
]
@@ -3558,7 +3558,7 @@ dependencies = [
"indexmap 2.11.1",
"log",
"once_cell",
"parking_lot",
"parking_lot 0.12.5",
"serde",
"serde_json",
"sha2 0.10.9",
@@ -3640,7 +3640,7 @@ dependencies = [
"indexmap 2.11.1",
"libc",
"memoffset",
"parking_lot",
"parking_lot 0.12.5",
"percent-encoding",
"pin-project",
"serde",
@@ -3885,7 +3885,7 @@ dependencies = [
"log",
"once_cell",
"os_pipe",
"parking_lot",
"parking_lot 0.12.5",
"pin-project",
"rand 0.8.5",
"tokio",
@@ -4265,7 +4265,7 @@ dependencies = [
"log",
"node_resolver",
"once_cell",
"parking_lot",
"parking_lot 0.12.5",
"sys_traits",
"thiserror 2.0.17",
"url",
@@ -4422,7 +4422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6742a724e8becb372a74c650a1aefb8924a5b8107f7d75b3848763ea24b27a87"
dependencies = [
"futures-util",
"parking_lot",
"parking_lot 0.12.5",
"tokio",
]
@@ -5667,7 +5667,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
"parking_lot 0.12.5",
]
[[package]]
@@ -6565,7 +6565,7 @@ dependencies = [
"ipconfig",
"moka",
"once_cell",
"parking_lot",
"parking_lot 0.12.5",
"rand 0.9.0",
"resolv-conf",
"serde",
@@ -7162,6 +7162,18 @@ dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "integer-encoding"
version = "3.0.4"
@@ -7621,7 +7633,7 @@ dependencies = [
"json-patch",
"k8s-openapi",
"kube-client",
"parking_lot",
"parking_lot 0.12.5",
"pin-project",
"serde",
"serde_json",
@@ -8135,6 +8147,12 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "matchit"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
[[package]]
name = "md-5"
version = "0.9.1"
@@ -8341,7 +8359,7 @@ dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"equivalent",
"parking_lot",
"parking_lot 0.12.5",
"portable-atomic",
"smallvec",
"tagptr",
@@ -9059,7 +9077,7 @@ dependencies = [
"hyper 1.8.1",
"itertools 0.14.0",
"md-5 0.10.6",
"parking_lot",
"parking_lot 0.12.5",
"percent-encoding",
"quick-xml 0.37.5",
"rand 0.9.0",
@@ -9473,6 +9491,17 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -9480,7 +9509,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.9.12",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
@@ -9959,6 +10002,64 @@ dependencies = [
"uuid",
]
[[package]]
name = "postgresql_archive"
version = "0.18.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c1e2f490f528e4eb06be6e9494ad28ea2b3f376bac7a9ad7c99b9062a7358e"
dependencies = [
"async-trait",
"flate2",
"futures-util",
"hex",
"num-format",
"regex-lite",
"reqwest 0.12.28",
"reqwest-middleware 0.4.2",
"reqwest-retry 0.7.0",
"reqwest-tracing",
"semver 1.0.27",
"serde",
"serde_json",
"sha2 0.10.9",
"tar",
"target-triple",
"tempfile",
"thiserror 2.0.17",
"tracing",
"url",
]
[[package]]
name = "postgresql_commands"
version = "0.18.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56eae883251fb55f23ff8e9f16121cfb2b00a56ebe62c56183c67904af26fa1d"
dependencies = [
"thiserror 2.0.17",
"tracing",
]
[[package]]
name = "postgresql_embedded"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d56d311cb5868a3d91ba40dab5f87050dfd462ee81c39616735eb1f992cb17"
dependencies = [
"anyhow",
"postgresql_archive",
"postgresql_commands",
"rand 0.9.0",
"semver 1.0.27",
"sqlx",
"target-triple",
"tempfile",
"thiserror 2.0.17",
"tokio",
"tracing",
"url",
]
[[package]]
name = "potential_utf"
version = "0.1.4"
@@ -10150,7 +10251,7 @@ dependencies = [
"fnv",
"lazy_static",
"memchr",
"parking_lot",
"parking_lot 0.12.5",
"thiserror 2.0.17",
]
@@ -10319,7 +10420,7 @@ dependencies = [
"ahash 0.8.12",
"equivalent",
"hashbrown 0.16.1",
"parking_lot",
"parking_lot 0.12.5",
]
[[package]]
@@ -10608,6 +10709,15 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -10811,6 +10921,21 @@ dependencies = [
"web-sys",
]
[[package]]
name = "reqwest-middleware"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e"
dependencies = [
"anyhow",
"async-trait",
"http 1.4.0",
"reqwest 0.12.28",
"serde",
"thiserror 1.0.69",
"tower-service",
]
[[package]]
name = "reqwest-middleware"
version = "0.5.0"
@@ -10826,6 +10951,28 @@ dependencies = [
"tower-service",
]
[[package]]
name = "reqwest-retry"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178"
dependencies = [
"anyhow",
"async-trait",
"futures",
"getrandom 0.2.16",
"http 1.4.0",
"hyper 1.8.1",
"parking_lot 0.11.2",
"reqwest 0.12.28",
"reqwest-middleware 0.4.2",
"retry-policies 0.4.0",
"thiserror 1.0.69",
"tokio",
"tracing",
"wasm-timer",
]
[[package]]
name = "reqwest-retry"
version = "0.9.0"
@@ -10839,20 +10986,45 @@ dependencies = [
"http 1.4.0",
"hyper 1.8.1",
"reqwest 0.13.1",
"reqwest-middleware",
"retry-policies",
"reqwest-middleware 0.5.0",
"retry-policies 0.5.0",
"thiserror 2.0.17",
"tokio",
"tracing",
"wasmtimer",
]
[[package]]
name = "reqwest-tracing"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d70ea85f131b2ee9874f0b160ac5976f8af75f3c9badfe0d955880257d10bd83"
dependencies = [
"anyhow",
"async-trait",
"getrandom 0.2.16",
"http 1.4.0",
"matchit 0.8.6",
"reqwest 0.12.28",
"reqwest-middleware 0.4.2",
"tracing",
]
[[package]]
name = "resolv-conf"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
[[package]]
name = "retry-policies"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c"
dependencies = [
"rand 0.8.5",
]
[[package]]
name = "retry-policies"
version = "0.5.0"
@@ -13401,6 +13573,12 @@ dependencies = [
"xattr",
]
[[package]]
name = "target-triple"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790"
[[package]]
name = "tempfile"
version = "3.24.0"
@@ -13707,7 +13885,7 @@ dependencies = [
"io-uring",
"libc",
"mio 1.1.1",
"parking_lot",
"parking_lot 0.12.5",
"pin-project-lite",
"signal-hook-registry",
"slab",
@@ -13772,7 +13950,7 @@ dependencies = [
"futures-channel",
"futures-util",
"log",
"parking_lot",
"parking_lot 0.12.5",
"percent-encoding",
"phf 0.11.3",
"pin-project-lite",
@@ -13798,7 +13976,7 @@ dependencies = [
"futures-channel",
"futures-util",
"log",
"parking_lot",
"parking_lot 0.12.5",
"percent-encoding",
"phf 0.11.3",
"pin-project-lite",
@@ -14074,7 +14252,7 @@ dependencies = [
"cookie 0.18.1",
"futures-util",
"http 1.4.0",
"parking_lot",
"parking_lot 0.12.5",
"pin-project-lite",
"tower-layer",
"tower-service",
@@ -14952,6 +15130,21 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot 0.11.2",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "wasm_dep_analyzer"
version = "0.2.0"
@@ -14970,7 +15163,7 @@ checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b"
dependencies = [
"futures",
"js-sys",
"parking_lot",
"parking_lot 0.12.5",
"pin-utils",
"slab",
"wasm-bindgen",
@@ -15048,7 +15241,7 @@ dependencies = [
"log",
"naga",
"once_cell",
"parking_lot",
"parking_lot 0.12.5",
"profiling",
"raw-window-handle",
"ron",
@@ -15090,7 +15283,7 @@ dependencies = [
"ndk-sys",
"objc",
"once_cell",
"parking_lot",
"parking_lot 0.12.5",
"profiling",
"range-alloc",
"raw-window-handle",
@@ -15189,7 +15382,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"aws-sdk-config",
@@ -15213,6 +15406,7 @@ dependencies = [
"object_store",
"once_cell",
"pep440_rs",
"postgresql_embedded",
"prometheus",
"quote",
"rand 0.9.0",
@@ -15252,7 +15446,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"argon2",
@@ -15304,7 +15498,7 @@ dependencies = [
"libxml",
"magic-crypt",
"mail-parser",
"matchit",
"matchit 0.7.3",
"mime_guess",
"native-tls",
"nkeys",
@@ -15374,7 +15568,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -15384,7 +15578,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"chrono",
"lazy_static",
@@ -15398,7 +15592,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"axum",
@@ -15417,7 +15611,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15468,13 +15662,14 @@ dependencies = [
"phf 0.11.3",
"pin-project-lite",
"postgres-native-tls 0.5.1",
"postgresql_embedded",
"prometheus",
"quick_cache",
"rand 0.9.0",
"regex",
"reqwest 0.13.1",
"reqwest-middleware",
"reqwest-retry",
"reqwest-middleware 0.5.0",
"reqwest-retry 0.9.0",
"semver 1.0.27",
"serde",
"serde_json",
@@ -15511,7 +15706,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"regex",
"serde",
@@ -15526,7 +15721,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -15550,7 +15745,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15566,7 +15761,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"reqwest 0.12.28",
@@ -15579,7 +15774,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"async-oauth2",
@@ -15603,7 +15798,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15612,7 +15807,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15624,7 +15819,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15636,7 +15831,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15648,7 +15843,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15660,7 +15855,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15672,7 +15867,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -15683,7 +15878,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15694,7 +15889,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15707,7 +15902,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15731,7 +15926,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15745,7 +15940,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -15762,7 +15957,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15776,7 +15971,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15795,7 +15990,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"serde",
@@ -15806,7 +16001,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15843,7 +16038,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -15853,7 +16048,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.603.2"
version = "1.603.0"
dependencies = [
"anyhow",
"async-once-cell",
@@ -15916,7 +16111,7 @@ dependencies = [
"rand 0.9.0",
"regex",
"reqwest 0.13.1",
"reqwest-middleware",
"reqwest-middleware 0.5.0",
"rust_decimal",
"serde",
"serde_json",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.603.2"
version = "1.603.0"
authors.workspace = true
edition.workspace = true
@@ -35,7 +35,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.603.2"
version = "1.603.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -86,6 +86,7 @@ oauth2 = ["windmill-api/oauth2"]
zip = ["windmill-api/zip"]
static_frontend = ["windmill-api/static_frontend"]
scoped_cache = ["windmill-common/scoped_cache"]
pg_embed = ["windmill-common/pg_embed", "dep:postgresql_embedded"]
test_job_debouncing = []
# Languages
python = ["windmill-worker/python", "windmill-api/python"]
@@ -161,7 +162,7 @@ k8s-openapi.workspace = true
libloading.workspace = true
bitflags.workspace = true
globset.workspace = true
postgresql_embedded = { version = "0.18.1", optional = true, features = ["theseus"], default-features = false }
[target.'cfg(windows)'.dependencies]
windows-service = "0.7"

View File

@@ -14,6 +14,55 @@ contains files used to build the "root" binary.
| [windmill-worker](./windmill-worker/) | The worker. Used to process and execute flows & jobs. |
| [parsers](./parsers/) | Contains code to parse signatures in different langauges. |
## Features
### Embedded PostgreSQL (`pg_embed`)
The `pg_embed` feature allows Windmill to run with an embedded PostgreSQL database, eliminating the need for a separate PostgreSQL instance. This is useful for local development, testing, or single-machine deployments.
**Building with pg_embed:**
```bash
cargo build --features pg_embed
```
**System Dependencies:**
The embedded PostgreSQL requires certain system libraries to be installed:
- **Arch Linux:**
```bash
sudo pacman -S libxml2 icu openssl
```
- **Ubuntu/Debian:**
```bash
sudo apt-get install libxml2 libicu-dev libssl-dev
```
- **RHEL/Fedora:**
```bash
sudo dnf install libxml2 libicu openssl-libs
```
**Usage:**
When the `pg_embed` feature is enabled, if `DATABASE_URL` is not set, Windmill will automatically start an embedded PostgreSQL instance.
You can customize the embedded PostgreSQL behavior with these environment variables:
- `PG_EMBED_DATA_DIR`: Directory for PostgreSQL data (default: `./postgresql_data`)
- `PG_EMBED_PORT`: Port for PostgreSQL (default: `5432`)
- `PG_EMBED_DATABASE`: Database name (default: `windmill`)
- `PG_EMBED`: Set to any value to force embedded PostgreSQL even if DATABASE_URL exists
**Example:**
```bash
# Run with default embedded PostgreSQL
./windmill
# Or with custom settings
PG_EMBED_DATA_DIR=/my/data PG_EMBED_PORT=5433 ./windmill
```
### Compile sqlx for offline ci
```

View File

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

View File

@@ -1,17 +0,0 @@
DO $$
DECLARE
dbname text := current_database();
BEGIN
-- Revoke default privileges first
ALTER DEFAULT PRIVILEGES IN SCHEMA public
REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM custom_instance_user;
REVOKE CREATE ON SCHEMA public FROM custom_instance_user;
REVOKE USAGE ON SCHEMA public FROM custom_instance_user;
EXECUTE format('REVOKE CREATE ON DATABASE %I FROM custom_instance_user', dbname);
EXECUTE format('REVOKE CONNECT ON DATABASE %I FROM custom_instance_user', dbname);
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Error in custom_instance_user migration: %', SQLERRM;
-- Continue without failing the migration
END
$$;

View File

@@ -332,6 +332,9 @@ fn print_help() {
println!(" cache [hubPaths.json] Pre-cache hub scripts (default: ./hubPaths.json)");
println!();
println!("Environment variables (name = default):");
#[cfg(feature = "pg_embed")]
println!(" DATABASE_URL = <optional> The Postgres database url (auto-generated with pg_embed if not set).");
#[cfg(not(feature = "pg_embed"))]
println!(" DATABASE_URL = <required> The Postgres database url.");
println!(" MODE = standalone Mode: standalone | worker | server | agent");
println!(" BASE_URL = http://localhost:8000 Public base URL of your instance (overridden by instance settings)");
@@ -339,16 +342,33 @@ fn print_help() {
println!(" SERVER_BIND_ADDR = {} IP to bind the server to", DEFAULT_SERVER_BIND_ADDR);
println!(" NUM_WORKERS = {} Number of workers (standalone/worker modes)", DEFAULT_NUM_WORKERS);
println!(" WORKER_GROUP = default Worker group this worker belongs to",);
println!(" QUIT_AFTER_FIRST_PING = false Exit worker after first successful ping (useful for testing)");
println!(" JSON_FMT = false Output logs in JSON instead of logfmt");
println!(" METRICS_ADDR = None (EE only) Prometheus metrics addr at /metrics; set \"true\" to use :8001");
println!(" SUPERADMIN_SECRET = None Virtual superadmin token (server)");
println!(" LICENSE_KEY = None (EE only) Enterprise license key (workers require valid key)");
println!(" RUN_UPDATE_CA_CERTIFICATE_AT_START = false Run system CA update at startup");
println!(" RUN_UPDATE_CA_CERTIFICATE_PATH = /usr/sbin/update-ca-certificates Path to CA update tool");
#[cfg(feature = "pg_embed")]
{
println!();
println!("Embedded PostgreSQL settings (pg_embed feature):");
println!(" PG_EMBED = <unset> Force embedded PostgreSQL even if DATABASE_URL exists");
println!(" PG_EMBED_DATA_DIR = ./postgresql_data Directory for embedded PostgreSQL data");
println!(" PG_EMBED_PORT = 5432 Port for embedded PostgreSQL");
println!(" PG_EMBED_DATABASE = windmill Database name for embedded PostgreSQL");
println!();
println!("System dependencies required for pg_embed:");
println!(" Arch: sudo pacman -S libxml2 icu openssl");
println!(" Ubuntu/Debian: sudo apt-get install libxml2 libicu-dev libssl-dev");
println!(" RHEL/Fedora: sudo dnf install libxml2 libicu openssl-libs");
}
println!();
println!("Notes:");
println!("- Advanced and less commonly used settings are managed via the database and are omitted here.");
println!("- At startup, Windmill logs currently set configuration keys for visibility.");
#[cfg(feature = "pg_embed")]
println!("- With pg_embed feature enabled, if DATABASE_URL is not set, an embedded PostgreSQL instance will be started automatically.");
}
async fn windmill_main() -> anyhow::Result<()> {
@@ -470,6 +490,15 @@ async fn windmill_main() -> anyhow::Result<()> {
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
};
// Initialize embedded PostgreSQL if pg_embed feature is enabled
#[cfg(feature = "pg_embed")]
let _embedded_pg = if std::env::var("PG_EMBED").is_ok() || (mode != Mode::Agent && std::env::var("DATABASE_URL").is_err() && std::env::var("DATABASE_URL_FILE").is_err()) {
println!("DATABASE_URL not set, starting embedded PostgreSQL...");
Some(windmill_common::pg_embed::init_embedded_postgres().await?)
} else {
None
};
let (conn, first_suffix) = if mode == Mode::Agent {
tracing::info!(
"Creating http client for cluster using base internal url {}",

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.603.2
version: 1.603.0
title: Windmill API
contact:
@@ -13532,12 +13532,6 @@ paths:
description: comma separated list of tags
schema:
type: string
- name: workspace
in: query
required: false
description: workspace to filter tags visibility (required when TAGS_ARE_SENSITIVE is enabled for non-superadmins)
schema:
type: string
responses:
"200":
description: map of tags to whether at least one worker with the tag exists

View File

@@ -56,7 +56,6 @@ lazy_static::lazy_static! {
(20251105100125, include_str!(
"../../migrations/20251105100125_legacy_sql_result_flag.up.sql"
).replace("", "")),
(20260107133344, "".to_string()),
].into_iter().collect();
}

View File

@@ -45,7 +45,7 @@ use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
use windmill_common::scripts::ScriptRunnableSettingsInline;
use windmill_common::triggers::TriggerMetadata;
use windmill_common::utils::{RunnableKind, WarnAfterExt};
use windmill_common::worker::{is_allowed_file_location, Connection, CLOUD_HOSTED, TMP_DIR};
use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR};
use windmill_common::workspace_dependencies::{
RawWorkspaceDependencies, MIN_VERSION_WORKSPACE_DEPENDENCIES,
};
@@ -7235,10 +7235,7 @@ impl Hash for JobUpdate {
}
async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::Result<Response> {
// Validate path to prevent directory traversal attacks (CVE pending)
let logs_dir = format!("{TMP_DIR}/logs");
let local_file = is_allowed_file_location(&logs_dir, &file_p)
.map_err(|_| error::Error::BadRequest("Invalid file path".to_string()))?;
let local_file = format!("{TMP_DIR}/logs/{file_p}");
if tokio::fs::metadata(&local_file).await.is_ok() {
let mut file = tokio::fs::File::open(local_file).await.map_err(to_anyhow)?;
let mut buffer = Vec::new();
@@ -7277,8 +7274,7 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
}
} else {
return Err(error::Error::internal_err(format!(
"Object store client not present and file not found on server logs volume at {}",
local_file.display()
"Object store client not present and file not found on server logs volume at {local_file}"
)));
}

View File

@@ -18,7 +18,6 @@ use uuid::Uuid;
use windmill_common::{
db::UserDB,
error::JsonResult,
jobs::TAGS_ARE_SENSITIVE,
utils::{paginate, Pagination},
worker::{ALL_TAGS, CUSTOM_TAGS_PER_WORKSPACE, DEFAULT_TAGS, DEFAULT_TAGS_PER_WORKSPACE},
DB,
@@ -113,64 +112,28 @@ async fn list_worker_pings(
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
let rows = if *TAGS_ARE_SENSITIVE && !is_super_admin {
rows.into_iter()
.map(|mut w| {
w.custom_tags = None;
w
})
.collect()
} else {
rows
};
Ok(Json(rows))
}
#[derive(Serialize, Deserialize)]
struct TagsQuery {
tags: String,
workspace: Option<String>,
}
async fn exists_workers_with_tags(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Query(tags_query): Query<TagsQuery>,
) -> JsonResult<std::collections::HashMap<String, bool>> {
// Create a list of requested tags
let mut tags: Vec<String> = tags_query
.tags
.split(',')
.map(|s| s.to_string())
.collect();
// When TAGS_ARE_SENSITIVE is enabled, filter tags based on workspace visibility
if *TAGS_ARE_SENSITIVE {
let is_super_admin = require_super_admin(&db, &authed.email).await.is_ok();
if !is_super_admin {
if let Some(ref workspace) = tags_query.workspace {
// Filter to only tags visible in this workspace
let custom_tags = CUSTOM_TAGS_PER_WORKSPACE.read().await;
let allowed_tags = custom_tags.to_string_vec(Some(workspace.clone()));
tags.retain(|t| allowed_tags.contains(t));
} else {
// No workspace provided and not superadmin - return empty
return Ok(Json(std::collections::HashMap::new()));
}
}
}
if tags.is_empty() {
return Ok(Json(std::collections::HashMap::new()));
}
let mut tx = user_db.begin(&authed).await?;
let mut result = std::collections::HashMap::new();
// Create a query that checks all tags at once using unnest
let tags = tags_query
.tags
.split(',')
.map(|s| s.to_string())
.collect::<Vec<String>>();
let rows = sqlx::query!(
"SELECT tag::text, EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> ARRAY[tag] AND ping_at > now() - interval '1 minute') as exists
FROM unnest($1::text[]) as tag",
@@ -192,11 +155,7 @@ struct CustomTagQuery {
workspace: Option<String>,
show_workspace_restriction: Option<bool>,
}
async fn get_custom_tags(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Query(query): Query<CustomTagQuery>,
) -> JsonResult<Vec<String>> {
async fn get_custom_tags(Query(query): Query<CustomTagQuery>) -> JsonResult<Vec<String>> {
if query.show_workspace_restriction.is_some_and(|x| x) && query.workspace.is_some() {
return Err(windmill_common::error::Error::BadRequest(
"Cannot use both workspace and show_workspace_restriction".to_string(),
@@ -211,12 +170,6 @@ async fn get_custom_tags(
let all_tags = tags_o.to_string_vec(None);
return Ok(Json(all_tags));
}
if *TAGS_ARE_SENSITIVE {
let is_super_admin = require_super_admin(&db, &authed.email).await.is_ok();
if !is_super_admin {
return Ok(Json(vec![]));
}
}
Ok(Json(ALL_TAGS.read().await.clone().into()))
}

View File

@@ -20,6 +20,7 @@ smtp = ["dep:mail-send"]
scoped_cache = []
cloud = []
openidconnect = ["dep:openidconnect"]
pg_embed = ["dep:postgresql_embedded"]
[lib]
name = "windmill_common"
path = "src/lib.rs"
@@ -114,6 +115,7 @@ opentelemetry = { workspace = true, optional = true }
tracing-opentelemetry = { workspace = true, optional = true }
opentelemetry-appender-tracing = { workspace = true, optional = true }
tonic = { workspace = true, optional = true }
postgresql_embedded = { version = "0.18.1", optional = true, features = ["theseus"], default-features = false }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemalloc-ctl = { optional = true, workspace = true }

View File

@@ -822,7 +822,7 @@ pub async fn get_logs_from_store(
}
lazy_static::lazy_static! {
pub static ref TAGS_ARE_SENSITIVE: bool = std::env::var("TAGS_ARE_SENSITIVE").map(
static ref TAGS_ARE_SENSITIVE: bool = std::env::var("TAGS_ARE_SENSITIVE").map(
|v| v.parse().unwrap()
).unwrap_or(false);
}

View File

@@ -73,6 +73,8 @@ pub mod oidc_oss;
#[cfg(feature = "private")]
pub mod otel_ee;
pub mod otel_oss;
#[cfg(feature = "pg_embed")]
pub mod pg_embed;
pub mod queue;
pub mod result_stream;
pub mod runnable_settings;

View File

@@ -0,0 +1,144 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2022
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use crate::error::Error;
use postgresql_embedded::{PostgreSQL, Settings};
use std::sync::Arc;
use tokio::sync::RwLock;
/// Manages an embedded PostgreSQL instance
pub struct EmbeddedPostgres {
postgresql: Arc<RwLock<PostgreSQL>>,
database_url: String,
}
impl EmbeddedPostgres {
/// Initialize and start an embedded PostgreSQL instance
pub async fn new() -> Result<Self, Error> {
tracing::info!("Initializing embedded PostgreSQL instance...");
// Configure the embedded PostgreSQL settings
let mut settings = Settings::default();
// Use environment variables if provided for customization
if let Ok(data_dir) = std::env::var("PG_EMBED_DATA_DIR") {
settings.installation_dir = data_dir.into();
}
if let Ok(port) = std::env::var("PG_EMBED_PORT") {
if let Ok(port_num) = port.parse::<u16>() {
settings.port = port_num;
}
}
let mut postgresql = PostgreSQL::new(settings);
// Setup the PostgreSQL instance
postgresql
.setup()
.await
.map_err(|e| {
let err_msg = format!("Failed to setup embedded PostgreSQL: {}", e);
if err_msg.contains("libxml2") || err_msg.contains("shared libraries") {
Error::InternalErr(format!(
"{}\n\n\
System dependencies are required for embedded PostgreSQL.\n\
On Arch Linux, install: sudo pacman -S libxml2 icu openssl\n\
On Ubuntu/Debian: sudo apt-get install libxml2 libicu-dev libssl-dev\n\
On RHEL/Fedora: sudo dnf install libxml2 libicu openssl-libs\n\n\
Alternatively, set DATABASE_URL to use an external PostgreSQL instance.",
err_msg
))
} else {
Error::InternalErr(err_msg)
}
})?;
tracing::info!("Starting embedded PostgreSQL...");
// Start the PostgreSQL instance
postgresql
.start()
.await
.map_err(|e| Error::InternalErr(format!("Failed to start embedded PostgreSQL: {}", e)))?;
tracing::info!("Embedded PostgreSQL started successfully");
// Get the database settings
let settings = postgresql.settings();
// Create the windmill database
let database_name = std::env::var("PG_EMBED_DATABASE")
.unwrap_or_else(|_| "windmill".to_string());
tracing::info!("Creating database: {}", database_name);
postgresql
.create_database(&database_name)
.await
.map_err(|e| {
Error::InternalErr(format!("Failed to create database '{}': {}", database_name, e))
})?;
// Build the connection string
let database_url = format!(
"postgres://{}:{}@{}:{}/{}",
settings.username,
settings.password,
settings.host,
settings.port,
database_name
);
tracing::info!("Embedded PostgreSQL ready at: postgres://{}:{}@{}:{}/{}",
settings.username,
"***", // Don't log password
settings.host,
settings.port,
database_name
);
Ok(Self {
postgresql: Arc::new(RwLock::new(postgresql)),
database_url,
})
}
/// Get the database connection URL
pub fn database_url(&self) -> &str {
&self.database_url
}
/// Stop the embedded PostgreSQL instance
pub async fn stop(&self) -> Result<(), Error> {
tracing::info!("Stopping embedded PostgreSQL...");
let pg = self.postgresql.write().await;
pg.stop()
.await
.map_err(|e| Error::InternalErr(format!("Failed to stop embedded PostgreSQL: {}", e)))?;
tracing::info!("Embedded PostgreSQL stopped");
Ok(())
}
}
/// Initialize an embedded PostgreSQL instance and set the DATABASE_URL environment variable
pub async fn init_embedded_postgres() -> Result<EmbeddedPostgres, Error> {
let embedded_pg = EmbeddedPostgres::new().await?;
// Set the DATABASE_URL environment variable so that the rest of the application
// can use it transparently
unsafe {
std::env::set_var("DATABASE_URL", embedded_pg.database_url());
}
tracing::info!("DATABASE_URL set to embedded PostgreSQL instance");
Ok(embedded_pg)
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ edition = "2024"
[dependencies]
chrono = "0.4.41"
duckdb = { version = "1.4.3", features = ["bundled"] }
duckdb = { rev = "fe0702529de6ec5a568337726bba9355503157d2", git = "https://github.com/windmill-labs/duckdb-rs.git", features = ["bundled"] }
rust_decimal = "1.37.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }

View File

@@ -267,7 +267,11 @@ fn do_duckdb_inner(
(0..stmt.column_count())
.map(|i| {
let logical_type = stmt.column_logical_type(i);
logical_type.get_alias()
if logical_type.is_invalid() {
None
} else {
logical_type.get_alias()
}
})
.collect::<Vec<_>>(),
);

View File

@@ -316,6 +316,11 @@ lazy_static::lazy_static! {
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
pub static ref QUIT_AFTER_FIRST_PING: bool = std::env::var("QUIT_AFTER_FIRST_PING")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
pub static ref UNSHARE_TINI_PATH: String = {
std::env::var("UNSHARE_TINI_PATH").unwrap_or_else(|_| "tini".to_string())
};
@@ -1265,6 +1270,14 @@ pub async fn run_worker(
.await
.expect("initial ping could be sent");
if *QUIT_AFTER_FIRST_PING {
tracing::info!(
worker = %worker_name, hostname = %hostname,
"QUIT_AFTER_FIRST_PING is set, exiting after successful initial ping"
);
return;
}
#[cfg(feature = "prometheus")]
let uptime_metric = if METRICS_ENABLED.load(Ordering::Relaxed) {
Some(

View File

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

View File

@@ -70,7 +70,7 @@ export {
// }
// });
export const VERSION = "1.603.2";
export const VERSION = "1.603.0";
// Re-exported from constants.ts to maintain backwards compatibility
export { WM_FORK_PREFIX } from "./core/constants.ts";

View File

@@ -26,8 +26,3 @@ RUN /usr/bin/java -jar /usr/bin/coursier about
# Ruby
RUN apt-get install -y ruby ruby-bundler
# Fix UV cache permissions for non-root user support (uid 1000, etc.)
# The uv tool install ansible command populates the UV cache with root-owned files
RUN chmod -R a+rw /tmp/windmill/cache/uv && \
find /tmp/windmill/cache/uv -type d -exec chmod 777 {} +

View File

@@ -50,8 +50,3 @@ RUN /usr/bin/java -jar /usr/bin/coursier about
# Ruby
RUN apt-get install -y ruby ruby-bundler
# Fix UV cache permissions for non-root user support (uid 1000, etc.)
# The uv tool install ansible command populates the UV cache with root-owned files
RUN chmod -R a+rw /tmp/windmill/cache/uv && \
find /tmp/windmill/cache/uv -type d -exec chmod 777 {} +

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.603.2",
"version": "1.603.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.603.2",
"version": "1.603.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.603.2",
"version": "1.603.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -75,10 +75,7 @@
clearTimeout(timeout)
}
if (open) {
tagsToWorkerExists = await WorkerService.existsWorkersWithTags({
tags: tags.join(','),
workspace: $workspaceStore
})
tagsToWorkerExists = await WorkerService.existsWorkersWithTags({ tags: tags.join(',') })
lastCheck = Date.now()
if (visible) {
timeout = setTimeout(() => {

View File

@@ -476,9 +476,5 @@ function normalizeNewFkToOldColNames(
}
export function dbSupportsTransactionalDdl(dbType: DbType): boolean {
return dbType === 'postgresql' || dbType === 'ms_sql_server'
// Commented out temporarily because Ducklake transactional DDL sometimes put the
// ducklake in an unusable state.
// See : https://github.com/duckdb/ducklake/issues/683
// || dbType === 'duckdb'
return dbType === 'postgresql' || dbType === 'ms_sql_server' || dbType === 'duckdb'
}

View File

@@ -3,7 +3,6 @@ import { wrapDucklakeQuery } from '$lib/components/ducklake'
import { runScriptAndPollResult } from '$lib/components/jobs/utils'
import type { ScriptLang } from '$lib/gen'
import type { TableEditorForeignKey } from '../tableEditor'
import type { TableMetadata } from '../utils'
/**
* Raw foreign key result from database queries
@@ -403,74 +402,57 @@ export async function fetchTableRelationalKeys(
schema: string | undefined,
workspace: string,
dbArg: Record<string, any>,
language: ScriptLang,
getColDefs: () => Promise<TableMetadata>
): Promise<{
foreignKeys: TableEditorForeignKey[]
pk_constraint_name?: string
colDefs: TableMetadata
}> {
let fkPromise = async () => {
try {
if (dbType !== 'bigquery') {
let fkQuery = makeForeignKeysQuery(dbType, table, schema)
if (input.type === 'ducklake') fkQuery = wrapDucklakeQuery(fkQuery, input.ducklake)
language: ScriptLang
): Promise<{ foreignKeys: TableEditorForeignKey[]; pk_constraint_name?: string }> {
let foreignKeys: TableEditorForeignKey[] = []
let pk_constraint_name: string | undefined = undefined
try {
if (dbType !== 'bigquery') {
let fkQuery = makeForeignKeysQuery(dbType, table, schema)
if (input.type === 'ducklake') fkQuery = wrapDucklakeQuery(fkQuery, input.ducklake)
const fkResult = await runScriptAndPollResult({
workspace,
requestBody: { args: dbArg, content: fkQuery, language }
})
const fkResult = await runScriptAndPollResult({
workspace,
requestBody: { args: dbArg, content: fkQuery, language }
})
let rawForeignKeys = fkResult as RawForeignKey[]
let rawForeignKeys = fkResult as RawForeignKey[]
if (rawForeignKeys && Array.isArray(rawForeignKeys)) {
// Lowercase keys for consistency
rawForeignKeys = rawForeignKeys.map((fk) => {
const lowerFk: any = {}
Object.keys(fk).forEach((key) => {
lowerFk[key.toLowerCase()] = fk[key]
})
return lowerFk
if (rawForeignKeys && Array.isArray(rawForeignKeys)) {
// Lowercase keys for consistency
rawForeignKeys = rawForeignKeys.map((fk) => {
const lowerFk: any = {}
Object.keys(fk).forEach((key) => {
lowerFk[key.toLowerCase()] = fk[key]
})
return transformForeignKeys(rawForeignKeys)
}
}
} catch (e) {
console.warn('Failed to fetch foreign keys:', e)
}
return []
}
let pkPromise = async () => {
try {
if (dbType !== 'bigquery' && dbType !== 'mysql') {
let pkQuery = makePrimaryKeyConstraintQuery(dbType, table, schema)
if (input.type === 'ducklake') pkQuery = wrapDucklakeQuery(pkQuery, input.ducklake)
const pkResult = await runScriptAndPollResult({
workspace,
requestBody: { args: dbArg, content: pkQuery, language }
return lowerFk
})
let rawPkResult = pkResult as RawPrimaryKeyConstraint[]
if (rawPkResult && Array.isArray(rawPkResult) && rawPkResult.length > 0) {
const pkRecord: any = rawPkResult[0]
const pk_constraint_name: string =
pkRecord?.constraint_name || pkRecord?.CONSTRAINT_NAME || ''
return pk_constraint_name
}
foreignKeys = transformForeignKeys(rawForeignKeys)
}
} catch (e) {
console.warn('Failed to fetch primary key constraint:', e)
}
} catch (e) {
console.warn('Failed to fetch foreign keys:', e)
}
const [foreignKeys, pk_constraint_name, colDefs] = await Promise.all([
fkPromise(),
pkPromise(),
getColDefs()
])
try {
if (dbType !== 'bigquery' && dbType !== 'mysql') {
let pkQuery = makePrimaryKeyConstraintQuery(dbType, table, schema)
if (input.type === 'ducklake') pkQuery = wrapDucklakeQuery(pkQuery, input.ducklake)
return { foreignKeys, pk_constraint_name, colDefs }
const pkResult = await runScriptAndPollResult({
workspace,
requestBody: { args: dbArg, content: pkQuery, language }
})
let rawPkResult = pkResult as RawPrimaryKeyConstraint[]
if (rawPkResult && Array.isArray(rawPkResult) && rawPkResult.length > 0) {
const pkRecord: any = rawPkResult[0]
pk_constraint_name = pkRecord?.constraint_name || pkRecord?.CONSTRAINT_NAME || ''
}
}
} catch (e) {
console.warn('Failed to fetch primary key constraint:', e)
}
return { foreignKeys, pk_constraint_name }
}

View File

@@ -191,15 +191,15 @@ export function dbSchemaOpsWithPreviewScripts({
})
},
onFetchTableEditorDefinition: async ({ table, schema, getColDefs }) => {
let { foreignKeys, pk_constraint_name, colDefs } = await fetchTableRelationalKeys(
let colDefs = await getColDefs()
let { foreignKeys, pk_constraint_name } = await fetchTableRelationalKeys(
input,
dbType,
table,
schema,
workspace,
dbArg,
language,
getColDefs
language
)
return buildTableEditorValues({

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { WorkerService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { AlertTriangle } from 'lucide-svelte'
import Popover from '../Popover.svelte'
import { onDestroy, untrack } from 'svelte'
@@ -29,10 +28,7 @@
async function lookForTag(): Promise<void> {
try {
if (!customTag) return
const existsWorkerWithTag = await WorkerService.existsWorkersWithTags({
tags: customTag,
workspace: $workspaceStore
})
const existsWorkerWithTag = await WorkerService.existsWorkersWithTags({ tags: customTag })
noWorkerWithTag = !existsWorkerWithTag[customTag]
if (noWorkerWithTag) {
timeout = setTimeout(() => {

View File

@@ -35,7 +35,6 @@
neverShowLoader?: boolean
loading?: boolean
loadingMore?: boolean
containerClass?: string
children?: import('svelte').Snippet
emptyMessage?: import('svelte').Snippet
}
@@ -60,7 +59,6 @@
neverShowLoader = false,
loading = false,
loadingMore = false,
containerClass = '',
children,
emptyMessage
}: Props = $props()
@@ -121,8 +119,7 @@
class={twMerge(
'h-full',
rounded ? 'rounded-md overflow-hidden' : '',
noBorder ? 'border-0' : 'border',
containerClass
noBorder ? 'border-0' : 'border'
)}
bind:clientHeight={tableHeight}
>

View File

@@ -352,12 +352,18 @@
class="cursor-not-allowed"
>
<svelte:fragment slot="trigger">
<ExploreAssetButton asset={{ kind: 'ducklake', path: '' }} disabled />
<ExploreAssetButton
class="h-9"
asset={{ kind: 'ducklake', path: ducklake.name }}
{dbManagerDrawer}
disabled
/>
</svelte:fragment>
<svelte:fragment slot="content">Please save settings first</svelte:fragment>
</Popover>
{:else}
<ExploreAssetButton
class="h-9"
asset={{ kind: 'ducklake', path: ducklake.name }}
{dbManagerDrawer}
/>

View File

@@ -1,9 +1,11 @@
<script lang="ts">
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { emptyString, pick, sendUserToast } from '$lib/utils'
import { emptyString, sendUserToast } from '$lib/utils'
import { ChevronDown, Plus, Shield } from 'lucide-svelte'
import Alert from '../common/alert/Alert.svelte'
import Button from '../common/button/Button.svelte'
import Tab from '../common/tabs/Tab.svelte'
import Tabs from '../common/tabs/Tabs.svelte'
import Description from '../Description.svelte'
import ResourcePicker from '../ResourcePicker.svelte'
import Toggle from '../Toggle.svelte'
@@ -23,27 +25,11 @@
import CloseButton from '../common/CloseButton.svelte'
import TextInput from '../text_input/TextInput.svelte'
import Select from '../select/Select.svelte'
import DataTable from '../table/DataTable.svelte'
import Head from '../table/Head.svelte'
import Cell from '../table/Cell.svelte'
import Row from '../table/Row.svelte'
import { deepEqual } from 'fast-equals'
import ExploreAssetButton from '../ExploreAssetButton.svelte'
import Modal2 from '../common/modal/Modal2.svelte'
let {
s3ResourceSettings = $bindable(),
s3ResourceSavedSettings,
onSave = undefined
}: {
s3ResourceSettings: S3ResourceSettings
s3ResourceSavedSettings: S3ResourceSettings
onSave?: () => void
} = $props()
let advancedPermissionModalState:
| { open: false }
| { open: true; storage: S3ResourceSettingsItem } = $state({ open: false })
}: { s3ResourceSettings: S3ResourceSettings; onSave?: () => void } = $props()
let s3FileViewer: S3FilePicker | undefined = $state()
@@ -59,42 +45,6 @@
sendUserToast(`Large file storage settings changed`)
onSave?.()
}
let tableHeadNames = ['Name', 'Storage resource', '', ''] as const
let tableHeadTooltips: Partial<Record<(typeof tableHeadNames)[number], string | undefined>> = {
'Storage resource':
'Which resource the workspace storage will point to. Note that all users of the workspace will be able to access the workspace storage regardless of the resource visibility.'
}
let tableRows: [string | null, S3ResourceSettingsItem][] = $derived([
[null, s3ResourceSettings],
...(s3ResourceSettings.secondaryStorage ?? [])
])
let secondaryStorageIsDirty: Record<string, boolean> = $derived(
Object.fromEntries(
s3ResourceSettings.secondaryStorage?.map((d) => {
const saved = s3ResourceSavedSettings.secondaryStorage?.find((saved) => saved[0] === d[0])
return [d[0], !deepEqual(saved?.[1], d[1])] as const
}) ?? []
)
)
let primaryStorageIsDirty: boolean = $derived.by(() => {
const fields = [
'resourcePath',
'resourceType',
'publicResource',
'advancedPermissions'
] as const
return !deepEqual(pick(s3ResourceSavedSettings, fields), pick(s3ResourceSettings, fields))
})
function isDirty(name: string | null): boolean {
return name === null ? primaryStorageIsDirty : secondaryStorageIsDirty[name]
}
function isPermissionsNonDefault(storage: S3ResourceSettingsItem): boolean {
const defaultPerms = defaultS3AdvancedPermissions(!!$enterpriseLicense)
return !deepEqual(storage.advancedPermissions, defaultPerms)
}
</script>
<Portal name="workspace-settings">
@@ -132,145 +82,140 @@
</Alert>
{/if}
{#if s3ResourceSettings}
<DataTable containerClass="mt-4">
<Head>
<tr>
{#each tableHeadNames as name, i}
<Cell head first={i == 0} last={i == tableHeadNames.length - 1}>
{name}
{#if tableHeadTooltips[name]}
<Tooltip>{@html tableHeadTooltips[name]}</Tooltip>
{/if}
</Cell>
{/each}
</tr>
</Head>
<tbody class="divide-y bg-surface">
{#each tableRows as tableRow, idx}
<Row>
<Cell first class="w-48 relative">
{#if tableRow[0] === null}
<TextInput inputProps={{ placeholder: 'Primary storage', disabled: true }} />
{:else}
<TextInput bind:value={tableRow[0]} inputProps={{ placeholder: 'Name' }} />
{/if}
</Cell>
<Cell>
<div class="flex gap-2">
<div class="relative">
<Select
items={[
{ value: 's3', label: 'S3' },
{ value: 'azure_blob', label: 'Azure Blob' },
{ value: 's3_aws_oidc', label: 'AWS OIDC' },
{ value: 'azure_workload_identity', label: 'Azure Workload Identity' },
{ value: 'gcloud_storage', label: 'Google Cloud Storage' }
]}
bind:value={tableRow[1].resourceType}
class="w-40"
/>
</div>
<div class="flex flex-1">
<ResourcePicker
class="flex-1"
bind:value={tableRow[1].resourcePath}
resourceType={tableRow[1].resourceType}
/>
</div>
</div>
</Cell>
<div class="mt-5">
<div class="w-full">
<!-- this can be removed once parent moves to runes -->
<!-- svelte-ignore binding_property_non_reactive -->
<Tabs bind:selected={s3ResourceSettings.resourceType}>
<Tab exact label="S3" value="s3" />
<Tab value="azure_blob" label="Azure Blob" />
<Tab exact value="s3_aws_oidc" label="AWS OIDC" />
<Tab value="azure_workload_identity" label="Azure Workload Identity" />
<Tab exact value="gcloud_storage" label="Google Cloud Storage" />
</Tabs>
</div>
<div class="w-full flex gap-1 mt-4 whitespace-nowrap">
<!-- this can be removed once parent moves to runes -->
<!-- svelte-ignore binding_property_non_reactive -->
<ResourcePicker
resourceType={s3ResourceSettings.resourceType}
bind:value={s3ResourceSettings.resourcePath}
/>
{@render permissionBtn(s3ResourceSettings)}
<Button
size="sm"
variant="accent"
disabled={emptyString(s3ResourceSettings.resourcePath)}
on:click={async () => {
if ($workspaceStore) {
s3FileViewer?.open?.(undefined)
}
}}>Browse content (save first)</Button
>
</div>
</div>
<Cell class="w-12">
<div class="flex gap-2">
<Button
variant="default"
btnClasses="px-2.5 relative"
size="sm"
onClick={() =>
(advancedPermissionModalState = { open: true, storage: tableRow[1] })}
>
<Shield size={16} /> Permissions <ChevronDown size={14} />
{#if isPermissionsNonDefault(tableRow[1])}
<span class="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-accent"
></span>
{/if}
</Button>
{#if emptyString(tableRow[1].resourcePath) || isDirty(tableRow[0])}
<Popover
openOnHover
contentClasses="p-2 text-sm text-secondary italic"
class="cursor-not-allowed"
>
<svelte:fragment slot="trigger">
<ExploreAssetButton asset={{ kind: 's3object', path: '' }} disabled />
</svelte:fragment>
<svelte:fragment slot="content">
{#if emptyString(tableRow[1].resourcePath)}
Please select a storage resource
{:else if isDirty(tableRow[0])}
Please save your changes
{/if}
</svelte:fragment>
</Popover>
{:else}
<ExploreAssetButton
asset={{ kind: 's3object', path: (tableRow[0] ?? '') + '/' }}
s3FilePicker={s3FileViewer}
/>
{/if}
</div>
</Cell>
<Cell class="w-12">
{#if tableRow[0] !== null}
<CloseButton
small
on:close={() => {
if (s3ResourceSettings.secondaryStorage) {
s3ResourceSettings.secondaryStorage.splice(idx - 1, 1)
s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage]
}
}}
/>
{/if}
</Cell>
</Row>
{/each}
<Row class="!border-0">
<Cell colspan={tableHeadNames.length} class="pt-0 pb-2">
<div class="flex justify-center">
<Button
size="sm"
btnClasses="max-w-fit"
variant="default"
on:click={() => {
if (s3ResourceSettings.secondaryStorage === undefined) {
s3ResourceSettings.secondaryStorage = []
<div class="mt-6">
<div class="flex mt-2 flex-col gap-y-4 max-w-5xl">
{#each s3ResourceSettings.secondaryStorage ?? [] as _, idx}
<div class="flex gap-1 relative whitespace-nowrap">
<TextInput
class="max-w-[200px]"
inputProps={{ type: 'text', placeholder: 'Storage name' }}
bind:value={
() => s3ResourceSettings.secondaryStorage?.[idx]?.[0] || '',
(v) => {
if (s3ResourceSettings.secondaryStorage?.[idx]) {
s3ResourceSettings.secondaryStorage[idx][0] = v
}
s3ResourceSettings.secondaryStorage.push([
`storage_${s3ResourceSettings.secondaryStorage.length + 1}`,
{
resourcePath: '',
resourceType: 's3',
publicResource: false,
advancedPermissions: defaultS3AdvancedPermissions(!!$enterpriseLicense)
}
])
s3ResourceSettings.secondaryStorage = s3ResourceSettings.secondaryStorage
}}
>
<Plus /> Add secondary storage
<Tooltip>
Secondary storage is a feature that allows you to read and write from storage that
isn't your main storage by specifying it in the s3 object as "secondary_storage"
with the name of it
</Tooltip>
</Button>
</div>
</Cell>
</Row>
</tbody>
</DataTable>
}
}
/>
<Select
class="max-w-[125px]"
inputClass="h-full"
bind:value={
() => s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourceType || 's3',
(v) => {
if (s3ResourceSettings.secondaryStorage?.[idx]) {
s3ResourceSettings.secondaryStorage[idx][1].resourceType = v
}
}
}
items={[
{ value: 's3', label: 'S3' },
{ value: 'azure_blob', label: 'Azure Blob' },
{ value: 's3_aws_oidc', label: 'AWS OIDC' },
{ value: 'azure_workload_identity', label: 'Azure Workload Identity' },
{ value: 'gcloud_storage', label: 'Google Cloud Storage' }
]}
/>
<ResourcePicker
resourceType={s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourceType || 's3'}
bind:value={
() => s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourcePath || undefined,
(v) => {
if (s3ResourceSettings.secondaryStorage?.[idx]) {
s3ResourceSettings.secondaryStorage[idx][1].resourcePath = v
}
}
}
/>
{@render permissionBtn(s3ResourceSettings.secondaryStorage![idx][1])}
<Button
size="sm"
variant="accent"
disabled={emptyString(s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourcePath)}
on:click={async () => {
if ($workspaceStore) {
s3FileViewer?.open?.({
s3: '',
storage: s3ResourceSettings.secondaryStorage?.[idx]?.[0] || ''
})
}
}}>Browse content (save first)</Button
>
<CloseButton
class="my-auto"
small
on:close={() => {
if (s3ResourceSettings.secondaryStorage) {
s3ResourceSettings.secondaryStorage.splice(idx, 1)
s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage]
}
}}
/>
</div>
{/each}
<div class="flex gap-1">
<Button
size="xs"
variant="default"
on:click={() => {
if (s3ResourceSettings.secondaryStorage === undefined) {
s3ResourceSettings.secondaryStorage = []
}
s3ResourceSettings.secondaryStorage.push([
`storage_${s3ResourceSettings.secondaryStorage.length + 1}`,
{
resourcePath: '',
resourceType: 's3',
publicResource: false,
advancedPermissions: defaultS3AdvancedPermissions(!!$enterpriseLicense)
}
])
s3ResourceSettings.secondaryStorage = s3ResourceSettings.secondaryStorage
}}><Plus size={14} />Add secondary storage</Button
>
<Tooltip>
Secondary storage is a feature that allows you to read and write from storage that isn't
your main storage by specifying it in the s3 object as "secondary_storage" with the name
of it
</Tooltip>
</div>
</div>
</div>
<div class="flex mt-5 mb-5 gap-1">
<Button
variant="accent"
@@ -283,98 +228,100 @@
</div>
{/if}
<Modal2
target="#content"
title={'Permission settings'}
contentClasses="flex flex-col gap-3"
fixedWidth="md"
fixedHeight="lg"
isOpen={advancedPermissionModalState.open}
>
{#if advancedPermissionModalState.open}
{@const storage = advancedPermissionModalState.storage}
{#if !$enterpriseLicense}
<Alert
type={storage.advancedPermissions ? 'error' : 'info'}
title="Advanced permission rules are an Enterprise feature"
>
Consider upgrading to Windmill EE to use advanced permission rules to control access to your
object storage at a more granular level.</Alert
>
{/if}
<Toggle
bind:checked={
() => !!storage.advancedPermissions,
(v) => {
storage.advancedPermissions = v
? defaultS3AdvancedPermissions(!!$enterpriseLicense)
: undefined
if (v) storage.publicResource = false
}
}
options={{
right: 'Enable advanced permission rules',
rightTooltip: 'Control precisely which paths are allowed to your users.'
}}
disabled={!storage.advancedPermissions && !$enterpriseLicense}
/>
{#if storage.advancedPermissions}
{@render advancedPermissionsEditor(storage.advancedPermissions)}
{/if}
{#if !storage.advancedPermissions}
{#if storage.resourceType == 's3'}
<div class="flex flex-col mt-2 mb-1 gap-1">
<Toggle
disabled={emptyString(storage.resourcePath)}
bind:checked={storage.publicResource}
options={{
right:
'S3 resource details and content can be accessed by all users of this workspace',
rightTooltip:
'If set, all users of this workspace will have access the to entire content of the S3 bucket, as well as the resource details and the "open preview" button. This effectively by-pass the permissions set on the resource and makes it public to everyone.'
}}
/>
{#if storage.publicResource === true}
<div class="pt-2"></div>
{#snippet permissionBtn(storage: NonNullable<S3ResourceSettings['secondaryStorage']>[number][1])}
<Popover closeOnOtherPopoverOpen placement="left">
<svelte:fragment slot="trigger">
<Button variant="default" wrapperClasses="h-full" btnClasses="px-2.5" size="sm">
<Shield size={16} /> Permissions <ChevronDown size={14} />
</Button>
</svelte:fragment>
<svelte:fragment slot="content">
<div class="flex flex-col gap-3 mx-4 pb-4 pt-5 w-[48rem]">
{#if !$enterpriseLicense}
<Alert
type={storage.advancedPermissions ? 'error' : 'info'}
title="Advanced permission rules are an Enterprise feature"
>
Consider upgrading to Windmill EE to use advanced permission rules to control access to
your object storage at a more granular level.</Alert
>
{/if}
<Toggle
bind:checked={
() => !!storage.advancedPermissions,
(v) => {
storage.advancedPermissions = v
? defaultS3AdvancedPermissions(!!$enterpriseLicense)
: undefined
if (v) storage.publicResource = false
}
}
options={{
right: 'Enable advanced permission rules',
rightTooltip: 'Control precisely which paths are allowed to your users.'
}}
disabled={!storage.advancedPermissions && !$enterpriseLicense}
/>
{#if storage.advancedPermissions}
{@render advancedPermissionsEditor(storage.advancedPermissions)}
{/if}
{#if !storage.advancedPermissions}
{#if storage.resourceType == 's3'}
<div class="flex flex-col mt-2 mb-1 gap-1">
<Toggle
disabled={emptyString(storage.resourcePath)}
bind:checked={storage.publicResource}
options={{
right:
'S3 resource details and content can be accessed by all users of this workspace',
rightTooltip:
'If set, all users of this workspace will have access the to entire content of the S3 bucket, as well as the resource details and the "open preview" button. This effectively by-pass the permissions set on the resource and makes it public to everyone.'
}}
/>
{#if storage.publicResource === true}
<div class="pt-2"></div>
<Alert
type="warning"
title="(Legacy) S3 bucket content and resource details are shared"
>
S3 resource public access is ON, which means that the entire content of the S3 bucket
will be accessible to all the users of this workspace regardless of whether they have
access the resource or not. Similarly, certain Windmill SDK endpoints can be used in
scripts to access the resource details, including public and private keys.
</Alert>
<Alert
type="warning"
title="(Legacy) S3 bucket content and resource details are shared"
>
S3 resource public access is ON, which means that the entire content of the S3
bucket will be accessible to all the users of this workspace regardless of whether
they have access the resource or not. Similarly, certain Windmill SDK endpoints
can be used in scripts to access the resource details, including public and
private keys.
</Alert>
{/if}
</div>
{:else}
<div class="flex flex-col mt-5 mb-1 gap-1 max-w-[40rem]">
<Toggle
disabled={emptyString(storage.resourcePath)}
bind:checked={storage.publicResource}
options={{
right: 'object storage content can be accessed by all users of this workspace',
rightTooltip:
'If set, all users of this workspace will have access the to entire content of the object storage.'
}}
/>
{#if storage.publicResource === true}
<div class="pt-2"></div>
<Alert
type="warning"
title="(Legacy) Object storage content and resource details are shared"
>
object public access is ON, which means that the entire content of the object
store will be accessible to all the users of this workspace regardless of whether
they have access the resource or not.
</Alert>
{/if}
</div>
{/if}
</div>
{:else}
<div class="flex flex-col mt-5 mb-1 gap-1 max-w-[40rem]">
<Toggle
disabled={emptyString(storage.resourcePath)}
bind:checked={storage.publicResource}
options={{
right: 'object storage content can be accessed by all users of this workspace',
rightTooltip:
'If set, all users of this workspace will have access the to entire content of the object storage.'
}}
/>
{#if storage.publicResource === true}
<div class="pt-2"></div>
<Alert
type="warning"
title="(Legacy) Object storage content and resource details are shared"
>
object public access is ON, which means that the entire content of the object store
will be accessible to all the users of this workspace regardless of whether they have
access the resource or not.
</Alert>
{/if}
</div>
{/if}
{/if}
{/if}
</Modal2>
{/if}
</div>
</svelte:fragment>
</Popover>
{/snippet}
{#snippet advancedPermissionsEditor(rules: S3ResourceSettingsItem['advancedPermissions'])}
<Alert title="Standard Unix-style glob syntax is supported">
@@ -388,22 +335,20 @@
<br />
Note that changes may take up to 1 minute to propagate due to cache invalidation
</Alert>
<div class="flex-1 overflow-y-auto gap-3 flex flex-col">
{#each rules ?? [] as item, idx}
<div class="flex gap-2">
<ClearableInput bind:value={item.pattern} placeholder="Pattern" />
<MultiSelect
items={[{ value: 'read' }, { value: 'write' }, { value: 'delete' }, { value: 'list' }]}
bind:value={item.allow}
class="w-[20rem]"
placeholder="Deny all access"
hideMainClearBtn
/>
<CloseButton onClick={() => rules?.splice(idx, 1)} />
</div>
{/each}
</div>
{#each rules ?? [] as item, idx}
<div class="flex gap-2">
<ClearableInput bind:value={item.pattern} placeholder="Pattern" />
<MultiSelect
items={[{ value: 'read' }, { value: 'write' }, { value: 'delete' }, { value: 'list' }]}
bind:value={item.allow}
disablePortal
class="w-[20rem]"
placeholder="Deny all access"
hideMainClearBtn
/>
<CloseButton onClick={() => rules?.splice(idx, 1)} />
</div>
{/each}
<Button size="xs" variant="default" on:click={() => rules?.push({ pattern: '', allow: [] })}>
<Plus size={14} />
Add permission rule

View File

@@ -1956,28 +1956,3 @@ export function countChars(str: string, char: string): number {
}
return count
}
export function buildReactiveObj<T extends object>(fields: {
[name in keyof T]: [() => T[name], (v: T[name]) => void]
}): T {
const obj = {} as T
for (const key in fields) {
Object.defineProperty(obj, key, {
get: fields[key][0],
set: fields[key][1],
enumerable: true,
configurable: true
})
}
return obj
}
export function pick<T extends object, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K> {
const result = {} as Pick<T, K>
for (const key of keys) {
if (key in obj) {
result[key] = obj[key]
}
}
return result
}

View File

@@ -120,7 +120,7 @@
publicResource: undefined,
secondaryStorage: undefined
})
let s3ResourceSavedSettings: S3ResourceSettings = $state({
let initialS3ResourceSettings: S3ResourceSettings = $state({
resourceType: 's3',
resourcePath: undefined,
publicResource: undefined,
@@ -353,7 +353,7 @@
settings.large_file_storage,
!!$enterpriseLicense
)
s3ResourceSavedSettings = clone(s3ResourceSettings)
initialS3ResourceSettings = clone(s3ResourceSettings)
dataTableSettings = convertDataTableSettingsFromBackend(settings.datatable)
ducklakeSettings = convertDucklakeSettingsFromBackend(settings.ducklake)
ducklakeSavedSettings = clone(ducklakeSettings)
@@ -580,7 +580,7 @@
}
const savedValue = {
s3ResourceSettings: s3ResourceSavedSettings,
s3ResourceSettings: initialS3ResourceSettings,
ducklakeSettings: ducklakeSavedSettings
}
@@ -594,7 +594,7 @@
// Function to discard unsaved storage settings changes
function discardStorageSettingsChanges() {
s3ResourceSettings = clone(s3ResourceSavedSettings)
s3ResourceSettings = clone(initialS3ResourceSettings)
ducklakeSettings = clone(ducklakeSavedSettings)
}
@@ -1203,9 +1203,8 @@
{:else if tab == 'windmill_lfs'}
<StorageSettings
bind:s3ResourceSettings
{s3ResourceSavedSettings}
onSave={() => {
s3ResourceSavedSettings = clone(s3ResourceSettings)
initialS3ResourceSettings = clone(s3ResourceSettings)
}}
/>
<DucklakeSettings

View File

@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.603.2"
wmill_pg = ">=1.603.2"
wmill = ">=1.603.0"
wmill_pg = ">=1.603.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: '3.0.3'
info:
version: 1.603.2
version: 1.603.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.603.2'
ModuleVersion = '1.603.0'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.603.2"
version = "1.603.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.603.2"
version = "1.603.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.603.2",
"version": "1.603.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"]

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.603.2",
"version": "1.603.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {

View File

@@ -1 +1 @@
1.603.2
1.603.0