Merge remote-tracking branch 'origin/main' into di/flow-input-redesign

This commit is contained in:
Diego Imbert
2025-09-22 14:35:43 +02:00
71 changed files with 650 additions and 696 deletions
+6 -6
View File
@@ -20,7 +20,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.88.0
toolchain: 1.90.0
- name: cargo check
working-directory: ./backend
timeout-minutes: 16
@@ -41,13 +41,13 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.88.0
toolchain: 1.90.0
- name: cargo check
working-directory: ./backend
timeout-minutes: 16
run: |
mkdir -p fake_frontend_build
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh)
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features all_sqlx_features
check_ee:
runs-on: ubicloud-standard-8
@@ -75,7 +75,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.88.0
toolchain: 1.90.0
- name: cargo check
working-directory: ./backend
timeout-minutes: 16
@@ -112,10 +112,10 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.88.0
toolchain: 1.90.0
- name: cargo check
timeout-minutes: 16
working-directory: ./backend
run: |
mkdir -p fake_frontend_build
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features all_sqlx_features,private
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.88.0
toolchain: 1.90.0
override: true
- name: Substitute EE code
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.88.0
toolchain: 1.90.0
- name: cargo check
working-directory: ./backend
+79
View File
@@ -117,6 +117,85 @@ jobs:
body: 'Successfully ran sqlx update'
})
demo:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/demo')
runs-on: ubicloud-standard-2
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Claude Code for Demo Generation
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
timeout_minutes: "10"
allowed_tools: "Bash"
direct_prompt: |
You need to:
1. Extract the Cloudflare preview URL from the cloudflare-workers-and-pages bot comment in this PR
2. Analyze the PR changes to understand what feature was added/modified
3. Create detailed demo instructions showing how to showcase the new feature
4. Create a demo.json file with a valid JSON object containing:
- instructions: the demo instructions
- url: the preview URL
5. VALIDATE the JSON file using `jq` before finishing
DO NOT COMMIT THIS FILE TO THE PR.
Example demo.json:
{
"instructions": "Instructions here",
"url": "https://example.pages.dev"
}
CRITICAL: After creating demo.json, you MUST:
1. Run `jq empty demo.json` to validate the JSON is properly formatted
2. If validation fails, fix the JSON and validate again
3. Only proceed once the JSON passes validation
4. Use proper JSON escaping for newlines, quotes, and special characters
Make sure to:
- Create a valid JSON object that passes `jq empty demo.json`
- Extract the correct preview URL (should be a .pages.dev domain)
- Create specific, actionable demo steps based on the actual changes in the PR
- Properly escape all strings in the JSON (use jq to create the file if needed)
- NOT COMMIT THE DEMO.JSON FILE TO THE PR
- name: Send instructions to Windmill
env:
DEMO_WEBHOOK_TOKEN: ${{ secrets.DEMO_WEBHOOK_TOKEN }}
run: |
if [[ -f "demo.json" ]]; then
echo "Found demo.json, sending to Windmill..."
cat demo.json
# Validate JSON one more time (Claude should have already done this)
if ! jq empty demo.json; then
echo "Error: demo.json is not valid JSON"
exit 1
fi
RESULT=$(curl -s \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $DEMO_WEBHOOK_TOKEN" \
-X POST \
-d @demo.json \
'https://app.windmill.dev/api/w/windmill-labs/jobs/run/f/f/ai/browserbase_demo')
echo "Windmill response:"
echo -E "$RESULT"
else
echo "Error: demo.json file not found"
exit 1
fi
update-ee-ref:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/eeref')
runs-on: ubicloud-standard-2
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.88.0
toolchain: 1.90.0
override: true
- name: Substitute EE code
+18
View File
@@ -1,5 +1,23 @@
# Changelog
## [1.545.0](https://github.com/windmill-labs/windmill/compare/v1.544.2...v1.545.0) (2025-09-20)
### Features
* force cancel in batch cancel ([2eeaf56](https://github.com/windmill-labs/windmill/commit/2eeaf5639dfcdb0114dc5fc323dbd10ef5ceaca5))
* load for loop jobs timeline directly from for loop flow status ([#6646](https://github.com/windmill-labs/windmill/issues/6646)) ([f71f9b0](https://github.com/windmill-labs/windmill/commit/f71f9b089438fc29dfb26a21ec2b8aa5867d0296))
### Bug Fixes
* add termination handler earlier in lifecycle ([56cdb69](https://github.com/windmill-labs/windmill/commit/56cdb69e599e295a51284c58d6ff56498ad22fd7))
* allow variable picker in password field ([194887e](https://github.com/windmill-labs/windmill/commit/194887e97b3250ef28b0ddf5c03932148858ca24))
* fix flow quick picker stuck ([#6638](https://github.com/windmill-labs/windmill/issues/6638)) ([5cab802](https://github.com/windmill-labs/windmill/commit/5cab802c421c5aeda8c2b57242a4e0c4c074a5f8))
* fix too strict aggrid coldef validation ([612d003](https://github.com/windmill-labs/windmill/commit/612d00367c2a288920c14449561433d199b95cae))
* retry python relative imports on errno 104 ([81ff0dc](https://github.com/windmill-labs/windmill/commit/81ff0dcd8cc2fb1c5fc12a558702cbc950ca1eb4))
* teams api improvements ([#6643](https://github.com/windmill-labs/windmill/issues/6643)) ([70e9ae1](https://github.com/windmill-labs/windmill/commit/70e9ae14a9541862aaa31b8435abb0185805d4b7))
## [1.544.2](https://github.com/windmill-labs/windmill/compare/v1.544.1...v1.544.2) (2025-09-18)
+1 -1
View File
@@ -1,5 +1,5 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.88-slim-bookworm
ARG RUST_IMAGE=rust:1.90-slim-bookworm
# Build libwindmill_duckdb_ffi_internal.so separately
FROM ${RUST_IMAGE} AS windmill_duckdb_ffi_internal_builder
@@ -1,19 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = \n CASE WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN\n JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT],\n $4\n ),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $5),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $6)\n ELSE\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4)\n END\n WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": []
},
"hash": "0088b5bf5cf1c7e47f18c3d05cce09f3462c8e39cb3291f0d497742ad4763fa3"
}
@@ -46,11 +46,11 @@
]
},
"nullable": [
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
true,
true
]
@@ -0,0 +1,19 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = \n CASE WHEN flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL THEN\n JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status,\n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT],\n $4\n ),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $5),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $6)\n ELSE\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4)\n END\n WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": []
},
"hash": "081f838b3dbe81631d17e7ca0751db725a7f92d4e43a86bcfa06a4ac7c70ac8f"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM (SKIP_LOCKED) v2_job_queue, v2_job_runtime, v2_job_status, job_perms",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "0cc221cb8b3059b21e6b3b4c874b8f4d32815edd2090ccb5d562a89142a7dd9c"
}
@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "hash",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f"
}
@@ -1,27 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = \n CASE \n WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN\n JSONB_SET(\n JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6),\n ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n )\n ELSE\n JSONB_SET(JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'iterator', 'index'],\n ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb\n ) END\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "2d0df658c31d45592dca7add8c69d06da7d81e7177a324e3f3f9af1076b3ab66"
}
@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc"
}
@@ -1,51 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Varchar",
"Varchar",
"Int8",
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf"
}
@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1"
}
@@ -0,0 +1,27 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = \n CASE \n WHEN flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL THEN\n JSONB_SET(\n JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n ELSE\n JSONB_SET(JSONB_SET(\n flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n END\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "93f00e2d164c090a959da12cbb7e4fc6e5804fea077116d339a041c0f2b0c1fa"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray",
"Jsonb"
]
},
"nullable": []
},
"hash": "a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887"
}
@@ -1,27 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_status SET flow_status = \n CASE \n WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN\n JSONB_SET(\n JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5), \n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n ELSE\n JSONB_SET(JSONB_SET(\n flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),\n ARRAY['modules', $1::TEXT, 'branchall', 'branch'],\n ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb\n )\n END\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "a49e59c814d440bfed7d6bfc2755518225b1243a9d39ce659b95637c33d0954f"
}
@@ -1,89 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Int8",
"Varchar",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript",
"aiagent"
]
}
}
},
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Jsonb"
]
},
"nullable": [
false
]
},
"hash": "ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338"
}
@@ -0,0 +1,27 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE v2_job_status SET flow_status = \n JSONB_SET(JSONB_SET(JSONB_SET(JSONB_SET(\n flow_status, \n ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4), \n ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5),\n ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "int4",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4",
"Uuid",
"Text",
"Jsonb",
"Jsonb",
"Jsonb"
]
},
"nullable": [
null
]
},
"hash": "ac5dd4a4d7991159e053f1c11bce03a6d51c24fc9a2d8ac872aaf41a20af5045"
}
@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
true,
false
false,
true
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": []
},
"hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46"
}
@@ -1,89 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Int8",
"Varchar",
{
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript",
"aiagent"
]
}
}
},
{
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
},
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Int4"
]
},
"nullable": [
false
]
},
"hash": "ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238"
}
+48 -47
View File
@@ -219,9 +219,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.99"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
@@ -1923,9 +1923,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.37"
version = "1.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -2032,9 +2032,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.47"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2042,9 +2042,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.47"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
@@ -5252,9 +5252,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
[[package]]
name = "fixedbitset"
@@ -11770,9 +11770,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.14.0"
version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -11790,11 +11790,11 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.14.0"
version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
dependencies = [
"darling 0.20.11",
"darling 0.21.3",
"proc-macro2",
"quote",
"syn 2.0.106",
@@ -13480,11 +13480,12 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.43"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
@@ -13893,9 +13894,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
dependencies = [
"winnow 0.7.13",
]
@@ -15128,7 +15129,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"aws-sdk-config",
@@ -15188,7 +15189,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"argon2",
@@ -15308,7 +15309,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -15323,7 +15324,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"chrono",
"serde",
@@ -15336,7 +15337,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"axum",
@@ -15355,7 +15356,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15439,7 +15440,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"regex",
"serde",
@@ -15454,7 +15455,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"bytes",
@@ -15478,7 +15479,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15490,7 +15491,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15499,7 +15500,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15511,7 +15512,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15523,7 +15524,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15535,7 +15536,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15547,7 +15548,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15559,7 +15560,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -15570,7 +15571,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15581,7 +15582,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15593,7 +15594,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15616,7 +15617,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15630,7 +15631,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -15647,7 +15648,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15661,7 +15662,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15679,7 +15680,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"getrandom 0.2.16",
@@ -15704,7 +15705,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15714,7 +15715,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15747,7 +15748,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -15757,7 +15758,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.544.2"
version = "1.545.0"
dependencies = [
"anyhow",
"async-once-cell",
+6 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.544.2"
version = "1.545.0"
authors.workspace = true
edition.workspace = true
@@ -34,7 +34,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.544.2"
version = "1.545.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -102,6 +102,10 @@ ruby = ["windmill-worker/ruby"]
all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java", "ruby"]
# For windows we have another set of languages enabled
all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "loki", "embedding", "parquet", "prometheus", "flow_testing",
"openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "nats", "otel", "dind", "websocket", "http_trigger",
"postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe",
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server"]
[patch.crates-io]
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
+1 -1
View File
@@ -1 +1 @@
1dc77acb47053b2dd84569d70884456de0188290
aa4c7936b99d0de2f7af31cde79dfc7b1b482c29
@@ -363,11 +363,11 @@ async fn parse_python_imports_inner(
// This way we make sure there is no multiple annotations for same script
// and we get detailed span on conflicting versions
#[derive(serde::Serialize, serde::Deserialize)]
struct InlineMetadata {
requires_python: String,
dependencies: Vec<String>,
}
// #[derive(serde::Serialize, serde::Deserialize)]
// struct InlineMetadata {
// requires_python: String,
// dependencies: Vec<String>,
// }
let find_requirements = code.lines().find_position(|x| {
x.starts_with("#requirements:")
+21 -11
View File
@@ -300,6 +300,19 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
}
async fn windmill_main() -> anyhow::Result<()> {
let (killpill_tx, mut killpill_rx) = KillpillSender::new(2);
let mut monitor_killpill_rx = killpill_tx.subscribe();
let (killpill_phase2_tx, _killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2);
let server_killpill_rx = killpill_phase2_tx.subscribe();
let shutdown_tx = killpill_tx.clone();
let shutdown_rx = killpill_tx.subscribe();
tokio::spawn(async move {
if let Err(e) = windmill_common::shutdown_signal(shutdown_tx, shutdown_rx).await {
tracing::error!("Error in shutdown signal: {e:#}");
}
});
dotenv::dotenv().ok();
update_ca_certificates_if_requested();
@@ -463,13 +476,18 @@ async fn windmill_main() -> anyhow::Result<()> {
if !skip_migration {
// migration code to avoid break
migration_handle = windmill_api::migrate_db(&db).await?;
migration_handle = windmill_api::migrate_db(&db, killpill_rx.resubscribe()).await?;
} else {
tracing::info!("SKIP_MIGRATION set, skipping db migration...")
}
}
}
if killpill_rx.try_recv().is_ok() {
tracing::info!("Received early killpill, aborting startup");
return Ok(());
}
let worker_mode = num_workers > 0;
let conn = if mode == Mode::Agent {
@@ -483,14 +501,6 @@ async fn windmill_main() -> anyhow::Result<()> {
Connection::Sql(db)
};
let (killpill_tx, mut killpill_rx) = KillpillSender::new(2);
let mut monitor_killpill_rx = killpill_tx.subscribe();
let (killpill_phase2_tx, _killpill_phase2_rx) = tokio::sync::broadcast::channel::<()>(2);
let server_killpill_rx = killpill_phase2_tx.subscribe();
let shutdown_signal =
windmill_common::shutdown_signal(killpill_tx.clone(), killpill_tx.subscribe());
#[cfg(feature = "enterprise")]
tracing::info!(
"
@@ -1257,10 +1267,9 @@ Windmill Community Edition {GIT_VERSION}
}
if mcp_mode {
futures::try_join!(shutdown_signal, workers_f, server_f)?;
futures::try_join!(workers_f, server_f)?;
} else {
futures::try_join!(
shutdown_signal,
workers_f,
monitor_f,
server_f,
@@ -1285,6 +1294,7 @@ Windmill Community Edition {GIT_VERSION}
}
}
}
std::process::exit(0);
Ok(())
}
+1 -1
View File
@@ -23,7 +23,7 @@ if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' 's/^# \(samael = { git="https:\/\/github.com\/njaremko\/samael", rev="464d015e3ae393e4b5dd00b4d6baa1b617de0dd6", features = \["xmlsec"\] }\)/\1/' Cargo.toml
fi
cargo sqlx prepare --workspace -- --all-targets --all-features
cargo sqlx prepare --workspace -- --all-targets --features all_sqlx_features,private
# Undo the samael changes on macOS
if [[ "$(uname)" == "Darwin" ]]; then
+5 -12
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.544.2
version: 1.545.0
title: Windmill API
contact:
@@ -7727,6 +7727,10 @@ paths:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: force_cancel
in: query
schema:
type: boolean
requestBody:
description: uuids of the jobs to cancel
required: true
@@ -18318,17 +18322,6 @@ components:
- script_path
- git_repo_resource_path
UploadFilePart:
type: object
properties:
part_number:
type: integer
tag:
type: string
required:
- part_number
- tag
MetricMetadata:
type: object
properties:
+8 -17
View File
@@ -147,14 +147,14 @@ fn is_false(b: &bool) -> bool {
!b
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct AppVersion {
pub id: i64,
pub app_id: Uuid,
pub value: sqlx::types::Json<Box<RawValue>>,
pub created_by: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
// #[derive(FromRow, Serialize, Deserialize)]
// pub struct AppVersion {
// pub id: i64,
// pub app_id: Uuid,
// pub value: sqlx::types::Json<Box<RawValue>>,
// pub created_by: String,
// pub created_at: chrono::DateTime<chrono::Utc>,
// }
#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct AppWithLastVersion {
@@ -180,15 +180,6 @@ pub struct AppWithLastVersionAndStarred {
pub starred: Option<bool>,
}
#[cfg(feature = "enterprise")]
#[derive(Debug, Serialize, FromRow)]
pub struct AppWithLastVersionAndWorkspace {
#[sqlx(flatten)]
#[serde(flatten)]
pub app: AppWithLastVersion,
pub workspace_id: String,
}
#[derive(Serialize, Deserialize, FromRow)]
pub struct AppWithLastVersionAndDraft {
#[sqlx(flatten)]
+37 -21
View File
@@ -101,14 +101,20 @@ impl Migrate for CustomMigrator {
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock($1)", lock_id)
.fetch_one(&mut *self.inner)
r = match tokio::time::timeout(std::time::Duration::from_secs(5), sqlx::query_scalar!("SELECT pg_try_advisory_lock($1)", lock_id)
.fetch_one(&mut *self.inner))
.await
.map_err(|e| {
tracing::error!("Error acquiring lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
{
Ok(Ok(r)) => r.unwrap_or(false),
Ok(Err(e)) => {
tracing::error!("Error acquiring lock: {e:#}");
return Err(sqlx::migrate::MigrateError::Execute(e));
}
Err(e) => {
tracing::error!("Timed out acquiring lock retrying in 5s: {e:#}");
false
}
};
if !r {
tracing::info!("PG migration lock already acquired by another server or worker, a migration is in progress, this may take a long time if you have many jobs and be normal, rechecking in 5s.");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
@@ -196,7 +202,10 @@ impl Migrate for CustomMigrator {
}
}
pub async fn migrate(db: &DB) -> Result<Option<JoinHandle<()>>, Error> {
pub async fn migrate(
db: &DB,
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> Result<Option<JoinHandle<()>>, Error> {
let migrator = db.acquire().await?;
let mut custom_migrator = CustomMigrator { inner: migrator };
@@ -211,20 +220,27 @@ pub async fn migrate(db: &DB) -> Result<Option<JoinHandle<()>>, Error> {
tracing::info!("Could not remove sqlx migrations: {err:#}");
}
match sqlx::migrate!("../migrations")
.run_direct(&mut custom_migrator)
.await
{
Ok(_) => Ok(()),
Err(sqlx::migrate::MigrateError::VersionMissing(e)) => {
tracing::error!("Database had been applied more migrations than this container.
This usually mean than another container on a more recent version migrated the database and this one is on an earlier version.
Please update the container to latest. Not critical, but may cause issues if migration introduced a breaking change. Version missing: {e:#}");
custom_migrator.unlock().await?;
Ok(())
tokio::select! {
_ = killpill_rx.recv() => {
tracing::info!("Killpill received, stopping migration");
return Ok(None);
}
Err(err) => Err(err),
}?;
migration_result = sqlx::migrate!("../migrations")
.run_direct(&mut custom_migrator)
=> {
match migration_result {
Ok(_) => Ok(()),
Err(sqlx::migrate::MigrateError::VersionMissing(e)) => {
tracing::error!("Database had been applied more migrations than this container.
This usually mean than another container on a more recent version migrated the database and this one is on an earlier version.
Please update the container to latest. Not critical, but may cause issues if migration introduced a breaking change. Version missing: {e:#}");
custom_migrator.unlock().await?;
Ok(())
}
Err(err) => Err(err),
}?;
}
}
return crate::live_migrations::custom_migrations(&mut custom_migrator, db).await;
}
@@ -30,15 +30,6 @@ use axum::response::Response;
#[cfg(all(feature = "parquet", not(feature = "private")))]
use serde::Deserialize;
#[derive(Deserialize)]
#[cfg(not(feature = "private"))]
pub struct LoadImagePreviewQuery {
#[allow(dead_code)]
pub file_key: String,
#[allow(dead_code)]
pub storage: Option<String>,
}
#[derive(Deserialize)]
#[cfg(not(feature = "private"))]
pub struct DownloadFileQuery {
+38 -3
View File
@@ -1751,6 +1751,7 @@ pub struct RunJobQuery {
pub timeout: Option<i32>,
pub cache_ttl: Option<i32>,
pub skip_preprocessor: Option<bool>,
pub poll_delay_ms: Option<u64>,
}
impl RunJobQuery {
@@ -2055,6 +2056,7 @@ async fn cancel_jobs(
db: &DB,
username: &str,
w_id: &str,
force_cancel: bool,
) -> error::JsonResult<Vec<Uuid>> {
let mut uuids = vec![];
let mut tx = db.begin().await?;
@@ -2114,7 +2116,7 @@ async fn cancel_jobs(
w_id,
tx,
db,
false,
force_cancel,
false,
)
.await?;
@@ -2145,11 +2147,17 @@ async fn cancel_jobs(
Ok(Json(uuids))
}
#[derive(Deserialize)]
pub struct CancelSelectionQuery {
force_cancel: Option<bool>,
}
async fn cancel_selection(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Query(query): Query<CancelSelectionQuery>,
Json(jobs): Json<Vec<Uuid>>,
) -> error::JsonResult<Vec<Uuid>> {
let mut tx = user_db.begin(&authed).await?;
@@ -2163,7 +2171,14 @@ async fn cancel_selection(
.await?;
tx.commit().await?;
cancel_jobs(jobs_to_cancel, &db, authed.username.as_str(), w_id.as_str()).await
cancel_jobs(
jobs_to_cancel,
&db,
authed.username.as_str(),
w_id.as_str(),
query.force_cancel.unwrap_or(false),
)
.await
}
async fn list_filtered_job_uuids(
@@ -5280,6 +5295,7 @@ pub async fn stream_job(
.to_args_from_runnable(&db, &w_id, runnable_id.clone(), run_query.skip_preprocessor)
.await?;
let poll_delay_ms = run_query.poll_delay_ms;
let uuid = match runnable_id {
RunnableId::ScriptId(ScriptId::ScriptPath(script_path))
| RunnableId::HubScript(script_path) => {
@@ -5349,6 +5365,7 @@ pub async fn stream_job(
None,
None,
tx,
poll_delay_ms,
);
let body = axum::body::Body::from_stream(stream.map(Result::<_, std::convert::Infallible>::Ok));
@@ -6521,6 +6538,7 @@ pub struct JobUpdateQuery {
pub only_result: Option<bool>,
pub fast: Option<bool>,
pub is_flow: Option<bool>,
pub poll_delay_ms: Option<u64>,
}
#[derive(Serialize, Debug)]
@@ -6682,6 +6700,7 @@ async fn get_job_update_sse(
only_result,
fast,
is_flow,
poll_delay_ms,
}): Query<JobUpdateQuery>,
) -> error::Result<Response> {
let (tx, rx) = tokio::sync::mpsc::channel(32);
@@ -6701,6 +6720,7 @@ async fn get_job_update_sse(
no_logs,
is_flow,
tx,
poll_delay_ms,
);
let stream = tokio_stream::wrappers::ReceiverStream::new(rx).map(|x| {
@@ -6746,6 +6766,7 @@ fn start_job_update_sse_stream(
no_logs: Option<bool>,
is_flow: Option<bool>,
tx: tokio::sync::mpsc::Sender<JobUpdateSSEStream>,
poll_delay_ms: Option<u64>,
) -> () {
tokio::spawn(async move {
let mut log_offset = initial_log_offset;
@@ -6828,13 +6849,27 @@ fn start_job_update_sse_stream(
loop {
i += 1;
let ms_duration = if i > 100 || !fast.unwrap_or(false) {
let mut ms_duration = if i > 100 || !fast.unwrap_or(false) {
3000
} else if i > 10 {
500
} else {
100
};
if let Some(poll_delay_ms) = poll_delay_ms {
#[cfg(feature = "enterprise")]
if poll_delay_ms < 50 {
tracing::warn!("Poll delay ms is less than 50, setting it to 50");
ms_duration = 50;
} else {
ms_duration = poll_delay_ms;
}
#[cfg(not(feature = "enterprise"))]
tracing::warn!("Settable poll delay requires EE");
}
if last_ping.elapsed().as_secs() > 5 {
if tx.send(JobUpdateSSEStream::Ping).await.is_err() {
tracing::warn!("Failed to send job ping for job {job_id}");
+12 -8
View File
@@ -100,10 +100,10 @@ mod inkeep_oss;
mod inputs;
mod integration;
mod live_migrations;
pub mod openapi;
#[cfg(all(feature = "private", feature = "parquet"))]
pub mod s3_proxy_ee;
mod s3_proxy_oss;
pub mod openapi;
mod approvals;
#[cfg(all(feature = "enterprise", feature = "private"))]
@@ -155,10 +155,10 @@ pub mod stripe_ee;
#[cfg(all(feature = "stripe", feature = "enterprise"))]
mod stripe_oss;
#[cfg(feature = "private")]
pub mod teams_ee;
#[cfg(feature = "private")]
pub mod teams_cache_ee;
mod teams_cache_oss;
#[cfg(feature = "private")]
pub mod teams_ee;
mod teams_oss;
mod token;
mod tracing_init;
@@ -696,9 +696,10 @@ pub async fn run_server(
name.map(|x| format!("name={x}")).unwrap_or_default()
);
port_tx
.send(format!("http://localhost:{}", port))
.expect("Failed to send port");
if let Err(e) = port_tx.send(format!("http://localhost:{}", port)) {
tracing::error!("Failed to send port: {e:#}");
return Err(anyhow::anyhow!("Failed to send port, exiting early: {e:#}"));
}
let server = server.with_graceful_shutdown(async move {
killpill_rx.recv().await.ok();
@@ -794,8 +795,11 @@ async fn openapi_json() -> Response {
.unwrap()
}
pub async fn migrate_db(db: &DB) -> anyhow::Result<Option<JoinHandle<()>>> {
db::migrate(db)
pub async fn migrate_db(
db: &DB,
killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> anyhow::Result<Option<JoinHandle<()>>> {
db::migrate(db, killpill_rx)
.await
.map_err(|e| anyhow::anyhow!("Error migrating db: {e:#}"))
}
+4 -4
View File
@@ -71,10 +71,10 @@ struct WorkerPing {
wm_memory_usage: Option<i64>,
}
#[derive(Serialize, Deserialize)]
struct EnableWorkerQuery {
disable: bool,
}
// #[derive(Serialize, Deserialize)]
// struct EnableWorkerQuery {
// disable: bool,
// }
#[derive(Deserialize)]
pub struct ListWorkerQuery {
+7 -7
View File
@@ -261,11 +261,11 @@ pub struct WorkspaceSettings {
pub auto_add_instance_groups_roles: Option<serde_json::Value>,
}
#[derive(sqlx::Type, Serialize, Deserialize, Debug)]
#[sqlx(type_name = "WORKSPACE_KEY_KIND", rename_all = "lowercase")]
pub enum WorkspaceKeyKind {
Cloud,
}
// #[derive(sqlx::Type, Serialize, Deserialize, Debug)]
// #[sqlx(type_name = "WORKSPACE_KEY_KIND", rename_all = "lowercase")]
// pub enum WorkspaceKeyKind {
// Cloud,
// }
#[derive(Deserialize)]
struct EditCommandScript {
@@ -1488,9 +1488,9 @@ async fn delete_git_sync_repository(
))
}
#[cfg(feature = "enterprise")]
#[derive(Debug, Deserialize)]
struct EditDeployUIConfig {
#[cfg(feature = "enterprise")]
deploy_ui_settings: Option<WorkspaceDeploymentUISettings>,
}
@@ -2553,7 +2553,7 @@ async fn clone_variables(
source_workspace_id: &str,
target_workspace_id: &str,
) -> Result<()> {
sqlx::query!(
sqlx::query!(
"INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms, account, is_oauth, expires_at)
SELECT $2, path, value, is_secret, description, extra_perms, account, is_oauth, expires_at
FROM variable
+40 -1
View File
@@ -18,7 +18,7 @@ use std::{
},
};
use tokio::sync::broadcast;
use tokio::{spawn, sync::broadcast};
use ee_oss::CriticalErrorChannel;
use error::Error;
@@ -198,8 +198,47 @@ pub async fn shutdown_signal(
},
}
spawn(async move {
#[cfg(any(target_os = "linux", target_os = "macos"))]
tokio::select! {
_ = terminate() => {
tracing::info!("2nd shutdown monitor received terminate");
},
_ = tokio::signal::ctrl_c() => {
tracing::info!("2nd shutdown monitor received ctrl-c");
},
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
tokio::select! {
_ = tokio::signal::ctrl_c() => {},
_ = rx.recv() => {
tracing::info!("2nd shutdown monitor received killpill");
},
}
tracing::info!("Second terminate signal received, forcefully exiting");
let handle = tokio::runtime::Handle::current();
let metrics = handle.metrics();
tracing::info!(
"Alive tasks: {}, global queue depth: {}",
metrics.num_alive_tasks(),
metrics.global_queue_depth()
);
std::process::exit(1);
});
tracing::info!("signal received, starting graceful shutdown");
let _ = tx.send();
spawn(async move {
tokio::time::sleep(std::time::Duration::from_hours(24 * 7)).await;
tracing::info!("Forcefully exiting after 7 days");
std::process::exit(1);
});
Ok(())
}
+26 -19
View File
@@ -51,25 +51,32 @@ class WindmillFinder(MetaPathFinder):
url = f"{os.environ.get('BASE_INTERNAL_URL')}/api/w/{os.environ.get('WM_WORKSPACE')}/scripts/raw/p/{script_path}.py{query_params}"
req = urllib.request.Request(url, None, headers)
try:
req_start = time.time()
with urllib.request.urlopen(req) as response:
os.makedirs(folder, exist_ok=True)
r = response.read().decode("utf-8")
if r == "WINDMILL_IS_FOLDER":
return ModuleSpec(name, WindmillLoader(name))
with open(fullpath, "w+") as f:
f.write(r)
return ModuleSpec(name, SourceFileLoader(name, fullpath))
except urllib.error.HTTPError as e:
duration = time.time() - req_start
if e.code != 404:
print(f"Error fetching script {script_path}: HTTP {e.code} - {e.reason} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
except Exception as e:
duration = time.time() - req_start
print(f"Error fetching script {script_path}: {e} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
for attempt in range(4): # 0, 1, 2, 3 = up to 3 retries
try:
req_start = time.time()
with urllib.request.urlopen(req) as response:
os.makedirs(folder, exist_ok=True)
r = response.read().decode("utf-8")
if r == "WINDMILL_IS_FOLDER":
return ModuleSpec(name, WindmillLoader(name))
with open(fullpath, "w+") as f:
f.write(r)
return ModuleSpec(name, SourceFileLoader(name, fullpath))
except urllib.error.HTTPError as e:
duration = time.time() - req_start
if e.code != 404:
print(f"Error fetching script {script_path}: HTTP {e.code} - {e.reason} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
except Exception as e:
duration = time.time() - req_start
# Check if this is errno 104 (Connection reset by peer) and we have retries left
if (hasattr(e, 'errno') and e.errno == 104) and attempt < 3:
print(f"Connection reset (errno 104) fetching script {script_path}, retrying in 3s (attempt {attempt + 1}/3)")
time.sleep(3)
continue
print(f"Error fetching script {script_path}: {e} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
+9 -2
View File
@@ -791,6 +791,7 @@ pub fn start_interactive_worker_shell(
loop {
if let Ok(_) = killpill_rx.try_recv() {
tracing::info!("Received killpill, exiting worker shell");
break;
} else {
let pulled_job = match &conn {
@@ -2041,8 +2042,14 @@ pub async fn run_worker(
}
tracing::info!(worker = %worker_name, hostname = %hostname, "waiting for interactive_shell to finish");
if let Some(interactive_shell) = interactive_shell {
if let Err(e) = interactive_shell.await {
tracing::error!("error in awaiting interactive_shell process: {e:?}")
match tokio::time::timeout(Duration::from_secs(10), interactive_shell).await {
Ok(Ok(_)) => {}
Ok(Err(e)) => {
tracing::error!("error in interactive_shell process: {e:?}")
}
Err(_) => {
tracing::error!("timed out awaiting interactive_shell process")
}
}
}
tracing::info!(worker = %worker_name, hostname = %hostname, "worker {} exited", worker_name);
+16 -30
View File
@@ -526,38 +526,25 @@ pub async fn update_flow_status_after_job_completion_internal(
} else {
None
};
tracing::error!(
"flow_job_duration: {:?}, position: {:?} flow: {:?}",
flow_job_duration,
position,
flow
);
let nindex = if let Some(position) = position {
sqlx::query_scalar!(
"UPDATE v2_job_status SET flow_status =
CASE
WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN
JSONB_SET(
JSONB_SET(JSONB_SET(JSONB_SET(
"
UPDATE v2_job_status SET flow_status =
JSONB_SET(JSONB_SET(JSONB_SET(JSONB_SET(
flow_status,
ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6),
ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb
)
ELSE
JSONB_SET(JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT], $4),
ARRAY['modules', $1::TEXT, 'iterator', 'index'],
((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb
) END
ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'started_at', $3::TEXT], $5),
ARRAY['modules', $1::TEXT, 'flow_jobs_duration', 'duration_ms', $3::TEXT], $6)
WHERE id = $2
RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int",
old_status.step,
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.started_at)),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms))
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
)
} else {
sqlx::query_scalar!(
@@ -578,8 +565,7 @@ pub async fn update_flow_status_after_job_completion_internal(
Error::internal_err(format!(
"error while fetching iterator index: {e:#}"
))
})?
.ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress")))?;
})?.ok_or_else(|| Error::internal_err(format!("requiring an index in InProgress for flow {flow} at step {}", old_status.step)))?;
// let status_for_debug = sqlx::query!(
// "SELECT flow_status FROM v2_job_status WHERE id = $1",
@@ -611,7 +597,7 @@ pub async fn update_flow_status_after_job_completion_internal(
sqlx::query_scalar!(
"UPDATE v2_job_status SET flow_status =
CASE
WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN
WHEN flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL THEN
JSONB_SET(
JSONB_SET(JSONB_SET(JSONB_SET(
flow_status,
@@ -634,8 +620,8 @@ pub async fn update_flow_status_after_job_completion_internal(
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.started_at)),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms))
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
)
} else {
sqlx::query_scalar!(
@@ -1599,7 +1585,7 @@ async fn set_success_and_duration_in_flow_job_success<'c>(
if let Some(position) = position {
sqlx::query!(
"UPDATE v2_job_status SET flow_status =
CASE WHEN flow_status->'modules'->$1::TEXT->'flow_jobs_duration' IS NOT NULL THEN
CASE WHEN flow_status->'modules'->$1::int->'flow_jobs_duration' IS NOT NULL THEN
JSONB_SET(JSONB_SET(JSONB_SET(
flow_status,
ARRAY['modules', $1::TEXT, 'flow_jobs_success', $3::TEXT],
@@ -1615,8 +1601,8 @@ async fn set_success_and_duration_in_flow_job_success<'c>(
flow,
position as i32,
json!(success),
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)),
flow_job_duration.as_ref().map(|x| json!(x.started_at))
flow_job_duration.as_ref().map(|x| json!(x.duration_ms)).unwrap_or_default(),
flow_job_duration.as_ref().map(|x| json!(x.started_at)).unwrap_or_default()
)
.execute(&mut **tx)
.await
+1 -1
View File
@@ -308,7 +308,7 @@ pub(crate) async fn queue_vacuum(conn: &Connection, worker_name: &str, hostname:
tokio::task::spawn(
(async move {
tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue");
if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms")
if let Err(e) = sqlx::query!("VACUUM (SKIP_LOCKED) v2_job_queue, v2_job_runtime, v2_job_status, job_perms")
.execute(&db2)
.await
{
+1 -1
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.544.2";
export const VERSION = "v1.545.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -68,7 +68,7 @@ export {
// }
// });
export const VERSION = "1.544.2";
export const VERSION = "1.545.0";
export const WM_FORK_PREFIX = "wm-fork";
+2 -2
View File
@@ -1,8 +1,8 @@
FROM ghcr.io/windmill-labs/windmill:dev
# Rust
COPY --from=rust:1.88.0 /usr/local/cargo /usr/local/cargo
COPY --from=rust:1.88.0 /usr/local/rustup /usr/local/rustup
COPY --from=rust:1.90.0 /usr/local/cargo /usr/local/cargo
COPY --from=rust:1.90.0 /usr/local/rustup /usr/local/rustup
RUN /usr/local/cargo/bin/cargo install cargo-sweep --version ^0.7
# Ansible
+2 -2
View File
@@ -20,8 +20,8 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
FROM ghcr.io/windmill-labs/windmill-ee:dev
# Rust
COPY --from=rust:1.88.0 /usr/local/cargo /usr/local/cargo
COPY --from=rust:1.88.0 /usr/local/rustup /usr/local/rustup
COPY --from=rust:1.90.0 /usr/local/cargo /usr/local/cargo
COPY --from=rust:1.90.0 /usr/local/rustup /usr/local/rustup
RUN /usr/local/cargo/bin/cargo install cargo-sweep --version ^0.7
# Ansible
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.544.2",
"version": "1.545.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.544.2",
"version": "1.545.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.544.2",
"version": "1.545.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
+16 -14
View File
@@ -541,7 +541,7 @@
<!-- svelte-ignore a11y_autofocus -->
<div
class={twMerge(
'flex flex-col w-full rounded-md relative',
'flex flex-col w-full rounded-md relative group',
minW ? 'min-w-[250px]' : '',
diffStatus?.diff ? 'px-2' : '',
diffStatus?.diff == 'added'
@@ -1344,19 +1344,21 @@
bind:value
></textarea>
{/key}
{#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<button
class="absolute right-1 top-1 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
onclick={() => {
pickForField = label
itemPicker?.openDrawer?.()
}}
title="Insert a Variable"
>
<DollarSign class="!text-tertiary" size={14} />
</button>
{/if}
{/if}
{#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<button
class="absolute {password || extra?.['password'] == true
? 'right-16 top-1.5'
: 'right-1 top-1'} opacity-0 group-hover:opacity-100 duration-200 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
onclick={() => {
pickForField = label
itemPicker?.openDrawer?.()
}}
title="Insert a Variable"
>
<DollarSign class="!text-tertiary" size={14} />
</button>
{/if}
</div>
{@render variableInput()}
@@ -176,7 +176,7 @@
<div class="pt-4 grow">
{#if jobId}
<FlowStatusViewer
bind:flowStateStore={flowStateStore.val}
bind:flowState={flowStateStore.val}
{jobId}
onJobsLoaded={({ job: newJob }) => {
job = newJob
@@ -583,7 +583,7 @@
bind:suspendStatus
hideDownloadInGraph={customUi?.downloadLogs === false}
wideResults
bind:flowStateStore={flowStateStore.val}
bind:flowState={flowStateStore.val}
{jobId}
onDone={() => {
isRunning = false
@@ -11,7 +11,7 @@
jobId: string
initialJob?: Job | undefined
workspaceId?: string | undefined
flowStateStore?: FlowState
flowState?: FlowState
selectedJobStep?: string | undefined
hideFlowResult?: boolean
hideTimeline?: boolean
@@ -39,7 +39,7 @@
jobId,
initialJob = undefined,
workspaceId = undefined,
flowStateStore = $bindable({}),
flowState = $bindable({}),
selectedJobStep = $bindable(undefined),
hideFlowResult = false,
hideTimeline = false,
@@ -67,7 +67,7 @@
let globalRefreshes: Record<string, ((clear, root) => Promise<void>)[]> = $state({})
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
flowStateStore,
flowState: flowState,
suspendStatus,
retryStatus,
hideDownloadInGraph,
@@ -88,7 +88,7 @@
retryStatus.val = {}
suspendStatus.val = {}
globalRefreshes = {}
flowStateStore.val = {}
flowState.val = {}
localDurationStatuses = {}
localModuleStates = {}
}
@@ -48,7 +48,7 @@
import JobAssetsViewer from './assets/JobAssetsViewer.svelte'
let {
flowStateStore,
flowState: flowStateStore,
retryStatus,
suspendStatus,
hideDownloadInGraph,
@@ -61,7 +61,6 @@
attempted_at: string
} | null = $state(null)
function showSetting(setting: string, values: Record<string, any>) {
if (setting == 'dev_instance') {
if (values['license_key'] == undefined) {
@@ -160,18 +159,19 @@
fetch_available_python_versions()
}
function handleTeamChange(
teamItem: { team_id: string; team_name: string } | undefined,
i: number
) {
$values['critical_error_channels'][i] = {
teams_channel: teamItem ? {
team_id: teamItem.team_id,
team_name: teamItem.team_name,
channel_id: undefined, // Will be set when channel is selected
channel_name: undefined
} : undefined
teams_channel: teamItem
? {
team_id: teamItem.team_id,
team_name: teamItem.team_name,
channel_id: undefined, // Will be set when channel is selected
channel_name: undefined
}
: undefined
}
}
@@ -191,7 +191,6 @@
}
}
}
</script>
<!-- {JSON.stringify($values, null, 2)} -->
@@ -367,7 +366,7 @@
<Password
small
placeholder={setting.placeholder}
on:keydown={() => {
onKeyDown={() => {
licenseKeyChanged = true
}}
bind:password={$values[setting.key]}
@@ -600,7 +599,8 @@
: undefined,
(channel) => handleChannelChange(channel, i)
}
onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)}
onError={(e) =>
sendUserToast('Failed to load channels: ' + e.message, true)}
/>
{/if}
</div>
@@ -645,8 +645,8 @@
if ($values[setting.key] == undefined || !Array.isArray($values[setting.key])) {
$values[setting.key] = []
}
// Start with a typed default to avoid invalid primitives in the array
$values[setting.key] = $values[setting.key].concat({ email: '' })
// Start with a typed default to avoid invalid primitives in the array
$values[setting.key] = $values[setting.key].concat({ email: '' })
}}
id="arg-input-add-item"
startIcon={{ icon: Plus }}
+24 -11
View File
@@ -1,15 +1,28 @@
<script lang="ts">
// @ts-nocheck
import { onMount } from 'svelte'
export let password: string | undefined
export let placeholder = '******'
export let disabled = false
export let required = false
export let small = false
import { createBubbler } from 'svelte/legacy'
$: red = required && (password == '' || password == undefined)
const bubble = createBubbler()
interface Props {
password: string | undefined
placeholder?: string
disabled?: boolean
required?: boolean
small?: boolean
onKeyDown?: (event: KeyboardEvent) => void
}
let hideValue = true
let {
password = $bindable(),
placeholder = '******',
disabled = false,
required = false,
small = false,
onKeyDown
}: Props = $props()
let red = $derived(required && (password == '' || password == undefined))
let hideValue = $state(true)
let randomId = (Math.random() * 10e15).toString(16)
</script>
@@ -29,7 +42,7 @@
: ''} text-sm h-9"
type="password"
bind:value={password}
on:keydown
onkeydown={onKeyDown}
autocomplete="new-password"
{placeholder}
{disabled}
@@ -41,7 +54,7 @@
: ''} text-sm h-9"
type="text"
bind:value={password}
on:keydown
onkeydown={bubble('keydown')}
autocomplete="new-password"
{placeholder}
{disabled}
@@ -2,24 +2,32 @@
import { VariableService } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { generateRandomString } from '$lib/utils'
import { Button } from './common'
import Password from './Password.svelte'
import { untrack } from 'svelte'
export let value: string | undefined = undefined
export let disabled: boolean
interface Props {
value?: string | undefined
disabled: boolean
}
let path = ''
let password = value && typeof value === 'string' && !value.startsWith('$var:') ? value : ''
let { value = $bindable(undefined), disabled }: Props = $props()
let path = $state('')
let password = $state(
value && typeof value === 'string' && !value.startsWith('$var:') ? value : ''
)
let isGenerating = false
let userPrefix = $derived(
'u/' + ($userStore?.username ?? $userStore?.email)?.split('@')[0] + '/secret_arg/'
)
async function generateValue() {
if (isGenerating) return
isGenerating = true
try {
let npath =
'u/' +
($userStore?.username ?? $userStore?.email)?.split('@')[0] +
'/secret_arg/' +
generateRandomString(12)
let npath = userPrefix + generateRandomString(12)
let nvalue = '$var:' + npath
await VariableService.createVariable({
workspace: $workspaceStore!,
@@ -32,6 +40,7 @@
}
})
path = npath
console.log('generated', nvalue)
value = nvalue
debouncedUpdate()
} finally {
@@ -59,13 +68,33 @@
timeout = setTimeout(updateValue, 500)
}
$: password && debouncedUpdate()
$effect(() => {
password && untrack(() => debouncedUpdate())
})
$: $workspaceStore &&
($userStore?.username || $userStore?.email) &&
path == '' &&
password != '' &&
generateValue()
$effect(() => {
$workspaceStore &&
($userStore?.username || $userStore?.email) &&
path == '' &&
password != '' &&
untrack(() => generateValue())
})
</script>
<Password {disabled} bind:password />
{#if value?.startsWith('$var:') && !value.startsWith('$var:' + userPrefix)}
<div class="flex items-center gap-2 text-sm text-tertiary">
Linked to static variable
<Button
color="light"
size="xs"
variant="border"
onclick={() => {
value = ''
}}
>
Reset variable link
</Button>
</div>
{:else}
<Password {disabled} bind:password />
{/if}
@@ -27,9 +27,9 @@ export abstract class AbstractCellRenderer implements ICellRendererComp {
eGui: any
protected component:
| {
refresh: (params: ICellRendererParams) => void
destroy: () => void
}
refresh: (params: ICellRendererParams) => void
destroy: () => void
}
| undefined
constructor(parentElement = 'span') {
// create empty span (or other element) to place svelte component in
@@ -275,7 +275,7 @@ export function validateColumnDefs(columnDefs: WindmillColumnDef[]): {
}
// Check if 'field' property exists and is a non-empty string
if (noField && !(colDef.children && Array.isArray(colDef.children))) {
if (noField && !(colDef.children && Array.isArray(colDef.children)) && !colDef.cellRenderer) {
isValid = false
errors.push(
`Column at index ${index} is missing a valid 'field' property nor having any children.`
@@ -6,7 +6,6 @@ import { charsToNumber, numberToChars } from './idUtils'
// Computes the next available id
export function nextId(flowState: FlowState, fullFlow: OpenFlow): string {
const allIds = dfs(fullFlow.value.modules, (fm) => fm.id)
console.log('allIds', allIds)
const max = allIds.concat(Object.keys(flowState)).reduce((acc, key) => {
if (key.length >= 4) {
+1 -1
View File
@@ -28,7 +28,7 @@ export type DurationStatus = {
}
export type FlowStatusViewerContext = {
flowStateStore?: FlowState
flowState?: FlowState
retryStatus: StateStore<Record<string, number | undefined>>
suspendStatus: StateStore<Record<string, { nb: number; job: Job }>>
hideDownloadInGraph?: boolean
@@ -70,7 +70,7 @@
jobKindsCat = undefined,
minTs = $bindable(undefined),
maxTs = $bindable(undefined),
jobKinds = $bindable(''),
jobKinds = $bindable(undefined),
queue_count = $bindable(undefined),
suspended_count = $bindable(undefined),
autoRefresh = true,
@@ -858,6 +858,7 @@
{:then Module}
<Module.default
showReset
onlyMaskPassword
disabled={!can_write}
schema={$state.snapshot(schema)}
bind:isValid
@@ -165,7 +165,7 @@
let queue_count: Tweened<number> | undefined = $state(undefined)
let suspended_count: Tweened<number> | undefined = $state(undefined)
let jobKinds: string | undefined = undefined
let jobKinds: string | undefined = $state(undefined)
let loading: boolean = $state(false)
let paths: string[] = $state([])
let usernames: string[] = $state([])
@@ -186,7 +186,7 @@
confirmBtnText: string
loading?: boolean
preContent?: string
onConfirm?: () => void
onConfirm?: (forceCancel: boolean) => void
type?: ConfirmationModal['$$prop_def']['type']
} = $state(undefined)
@@ -544,10 +544,11 @@
}
}
async function cancelJobs(uuidsToCancel: string[]) {
async function cancelJobs(uuidsToCancel: string[], forceCancel: boolean = false) {
const uuids = await JobService.cancelSelection({
workspace: $workspaceStore ?? '',
requestBody: uuidsToCancel
requestBody: uuidsToCancel,
forceCancel: forceCancel
})
selectedIds = []
jobsLoader?.loadJobs(minTs, maxTs, true, true)
@@ -556,6 +557,7 @@
}
async function onCancelFilteredJobs() {
forceCancelInPopup = false
askingForConfirmation = {
title: 'Confirm cancelling all jobs corresponding to the selected filters',
confirmBtnText: 'Loading...',
@@ -570,18 +572,19 @@
title: `Confirm cancelling all jobs corresponding to the selected filters (${jobIdsToCancel.length} jobs)`,
confirmBtnText: `Cancel ${jobIdsToCancel.length} jobs that matched the filters`,
preContent: selectedFiltersString,
onConfirm: () => {
cancelJobs(jobIdsToCancel)
onConfirm: (forceCancel) => {
cancelJobs(jobIdsToCancel, forceCancel)
}
}
}
async function onCancelSelectedJobs() {
forceCancelInPopup = true
askingForConfirmation = {
confirmBtnText: `Cancel ${selectedIds.length} jobs`,
title: 'Confirm cancelling the selected jobs',
onConfirm: () => {
cancelJobs(selectedIds)
onConfirm: (forceCancel) => {
cancelJobs(selectedIds, forceCancel)
}
}
}
@@ -798,6 +801,8 @@
const smallScreenWidth = 1920
const verySmallScreenWidth = 1300
let forceCancelInPopup = $state(false)
</script>
<JobsLoader
@@ -820,7 +825,7 @@
computeMinAndMax={manualDatePicker?.computeMinMax}
bind:minTs
bind:maxTs
{jobKinds}
bind:jobKinds
bind:queue_count
bind:suspended_count
{autoRefresh}
@@ -842,7 +847,7 @@
open={!!askingForConfirmation}
on:confirmed={async () => {
const func = askingForConfirmation?.onConfirm
await func?.()
await func?.(forceCancelInPopup)
askingForConfirmation = undefined
}}
type={askingForConfirmation?.type}
@@ -853,6 +858,27 @@
>
{#if askingForConfirmation?.preContent}
<pre>{askingForConfirmation.preContent}</pre>
<Toggle
size="xs"
class="mt-4"
color="red"
bind:checked={forceCancelInPopup}
options={{
right: 'Force cancel',
rightTooltip:
'Only use this for jobs that refuse to gracefully cancel. This is dangerous, only do this if you have no alternatives!'
}}
></Toggle>
{#if forceCancelInPopup}
<div class="mt-4 text-red-500 p-2 text-sm">
<p>
Force cancel is enabled. This is dangerous, only do this if you have no alternatives.
Instead of being gracefully cancelled, all jobs will be immediately sent to the completed
job table regardless of them being processed or not or part of running flows. You may end
up in an inconsistent state.
</p>
</div>
{/if}
{/if}
</ConfirmationModal>
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.544.2"
wmill_pg = ">=1.544.2"
wmill = ">=1.545.0"
wmill_pg = ">=1.545.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.544.2
version: 1.545.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.544.2'
ModuleVersion = '1.545.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.544.2"
version = "1.545.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.544.2"
version = "1.545.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.544.2",
"version": "1.545.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.544.2",
"version": "1.545.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.544.2
1.545.0