Compare commits
1 Commits
v1.426.0
...
rf/flowSta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0448ac838 |
11
.github/workflows/build-publish-rh-image.yml
vendored
11
.github/workflows/build-publish-rh-image.yml
vendored
@@ -3,7 +3,8 @@ env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Build and publish windmill for RHEL9
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
permissions: write-all
|
||||
|
||||
@@ -64,7 +65,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -73,7 +74,7 @@ jobs:
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}-amd64
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
|
||||
- name: Build and push publicly ee arm64
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
|
||||
secrets: |
|
||||
rh_username=${{ secrets.RH_USERNAME }}
|
||||
rh_password=${{ secrets.RH_PASSWORD }}
|
||||
@@ -107,7 +108,7 @@ jobs:
|
||||
run: |
|
||||
mv "${{ steps.extract-ee-amd64.outputs.destination }}/windmill" "${{ steps.extract-ee-amd64.outputs.destination }}/windmill-ee-amd64-rhel9"
|
||||
mv "${{ steps.extract-ee-arm64.outputs.destination }}/windmill" "${{ steps.extract-ee-arm64.outputs.destination }}/windmill-ee-arm64-rhel9"
|
||||
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: RHEL9-amd64 build
|
||||
|
||||
2
.github/workflows/build-staging-image.yml
vendored
2
.github/workflows/build-staging-image.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,kafka
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
|
||||
2
.github/workflows/build_windows_worker_.yml
vendored
2
.github/workflows/build_windows_worker_.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
10
.github/workflows/docker-image.yml
vendored
10
.github/workflows/docker-image.yml
vendored
@@ -1,8 +1,10 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository ||
|
||||
IMAGE_NAME:
|
||||
${{ github.event_name != 'pull_request' && github.repository ||
|
||||
'windmill-labs/windmill-test' }}
|
||||
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
|
||||
DEV_SHA:
|
||||
${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
|
||||
github.event.number) }}
|
||||
|
||||
name: Build windmill:main
|
||||
@@ -138,7 +140,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
@@ -200,7 +202,7 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka
|
||||
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
PYTHON_IMAGE=python:3.12.2-slim-bookworm
|
||||
tags: |
|
||||
${{ steps.meta-ee-public-py312.outputs.tags }}
|
||||
|
||||
2
.github/workflows/publish_windows_worker.yml
vendored
2
.github/workflows/publish_windows_worker.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,kafka
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,18 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.426.0](https://github.com/windmill-labs/windmill/compare/v1.425.1...v1.426.0) (2024-11-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add mode (permissions) option to files in ansible ([#4724](https://github.com/windmill-labs/windmill/issues/4724)) ([5e10782](https://github.com/windmill-labs/windmill/commit/5e107827790292d89e51d81138eeef2b5b23a9c2))
|
||||
* kafka triggers ([#4713](https://github.com/windmill-labs/windmill/issues/4713)) ([88b8ffa](https://github.com/windmill-labs/windmill/commit/88b8ffab907e662ccca612bf01e6a228566522d2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not mount critical alerts modal if user is neither superadmin nor workspace admin ([#4731](https://github.com/windmill-labs/windmill/issues/4731)) ([180809d](https://github.com/windmill-labs/windmill/commit/180809d3466fb62d6692c45cbb012f158f83528f))
|
||||
|
||||
## [1.425.1](https://github.com/windmill-labs/windmill/compare/v1.425.0...v1.425.1) (2024-11-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM kafka_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2139f1fb1877294bbf55d786000c5c32f582a3911efcf88e437aa90d7d5a49b5"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM kafka_trigger WHERE workspace_id = $1 AND path = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3997dcf2c11817e59bf50fd896381d870b847a8ba07e6197212fdf85ec901b09"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE kafka_trigger SET enabled = FALSE, error = $1, server_id = NULL, last_server_ping = NULL WHERE workspace_id = $2 AND path = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3e64c894c89ef82c4527180b82c4e82c1b7060ba0ca288dc6a7c7afcd76212e8"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE kafka_trigger SET kafka_resource_path = $1, group_id = $2, topics = $3, script_path = $4, path = $5, is_flow = $6, edited_by = $7, email = $8, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE workspace_id = $9 AND path = $10",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5962733746c81480abe9ab3a6ccf5664130ae7500279055c08db42d67b2822f6"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM kafka_trigger WHERE path = $1 AND workspace_id = $2)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6d418b5cd7a4df54cfe3ec06e2a957c87f2307a218115ca749ea0e71eca2002e"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE kafka_trigger SET last_server_ping = now(), error = $1 WHERE workspace_id = $2 AND path = $3 AND server_id = $4 AND enabled IS TRUE RETURNING 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6e70ebf078ac04a2933d2f83791973e8fc108d9f32be8a8501391052d76e191e"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE kafka_trigger SET enabled = $1, email = $2, edited_by = $3, edited_at = now(), server_id = NULL, last_server_ping = NULL, error = NULL\n WHERE path = $4 AND workspace_id = $5 RETURNING 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "6eb076afcf11dba0dfe35ee108c64814f95dbe5376f0539195d24a8ca96ca3e2"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "websocket_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "http_routes_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "kafka_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cc8a71abdeccf0787695af32aa21fe73aba65cfdc8329c02b4d257de4d2d168a"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE kafka_trigger SET server_id = $1, last_server_ping = now() WHERE enabled IS TRUE AND workspace_id = $2 AND path = $3 AND (server_id IS NULL OR last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') RETURNING true",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d6615719bf8db4b333ed55c9a3c160ad1962668fc3305d8e80ae91ef73614a80"
|
||||
}
|
||||
28
backend/.sqlx/query-db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53.json
generated
Normal file
28
backend/.sqlx/query-db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "websocket_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "http_routes_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "db24f1f6ef1e26b7de7926bd3f32d1fa673c4970c25ff9ad98f0fdf199801b53"
|
||||
}
|
||||
162
backend/Cargo.lock
generated
162
backend/Cargo.lock
generated
@@ -1032,9 +1032,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
checksum = "49c41b948da08fb481a94546cd874843adc1142278b0af4badf9b1b78599d68d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -1378,7 +1378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -2776,7 +2776,7 @@ checksum = "22a1abc6bd8af41aa2496ceceef94f4277092c781a9495c437327a17659553a2"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"deno_native_certs",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-tokio-stream",
|
||||
"rustls-webpki 0.102.8",
|
||||
@@ -4295,7 +4295,7 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"hyper 1.5.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -4831,9 +4831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.164"
|
||||
version = "0.2.163"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
checksum = "1fdaeca4cf44ed4ac623e86ef41f056e848dbeab7ec043ecb7326ba300b36fd0"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
@@ -5054,7 +5054,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"gethostname",
|
||||
"mail-builder",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"smtp-proto",
|
||||
"tokio",
|
||||
@@ -5313,7 +5313,7 @@ dependencies = [
|
||||
"darling 0.20.10",
|
||||
"heck 0.5.0",
|
||||
"num-bigint",
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5556,27 +5556,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
@@ -5757,15 +5736,6 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.4.1+3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
@@ -5774,7 +5744,6 @@ checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -6270,16 +6239,6 @@ dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
@@ -6569,7 +6528,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"socket2 0.5.7",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
@@ -6587,7 +6546,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.3",
|
||||
@@ -6752,38 +6711,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdkafka"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1beea247b9a7600a81d4cc33f659ce1a77e1988323d7d2809c7ed1c21f4c316d"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"rdkafka-sys",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"slab",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdkafka-sys"
|
||||
version = "4.7.0+2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55e0d2f9ba6253f6ec72385e453294f8618e9e15c2c6aba2a5c01ccf9622d615"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"num_enum",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reborrow"
|
||||
version = "0.5.5"
|
||||
@@ -6991,7 +6918,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
@@ -7292,9 +7219,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.17"
|
||||
version = "0.23.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e"
|
||||
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -7377,7 +7304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
]
|
||||
@@ -7721,9 +7648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.133"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
@@ -8269,7 +8196,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9543,7 +9470,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
@@ -10284,7 +10211,7 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"rustls 0.23.17",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -10683,7 +10610,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -10725,7 +10652,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -10768,7 +10695,6 @@ dependencies = [
|
||||
"prometheus",
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"rdkafka",
|
||||
"regex",
|
||||
"reqwest 0.12.9",
|
||||
"rsa 0.7.2",
|
||||
@@ -10811,7 +10737,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -10829,7 +10755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -10842,7 +10768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rsmq_async",
|
||||
@@ -10857,7 +10783,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -10904,7 +10830,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"rsmq_async",
|
||||
@@ -10919,7 +10845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -10941,7 +10867,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
@@ -10953,7 +10879,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -10962,7 +10888,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10974,7 +10900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -10986,7 +10912,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10998,7 +10924,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -11009,7 +10935,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -11020,7 +10946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11038,7 +10964,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -11055,7 +10981,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11067,7 +10993,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -11085,7 +11011,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.15",
|
||||
@@ -11106,7 +11032,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -11116,7 +11042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11149,7 +11075,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -11159,7 +11085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -29,7 +29,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -62,7 +62,6 @@ jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemal
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy"]
|
||||
sqlx = ["windmill-worker/sqlx"]
|
||||
deno_core = ["windmill-worker/deno_core", "dep:deno_core"]
|
||||
kafka = ["windmill-api/kafka"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -267,7 +266,6 @@ tokio-native-tls = "^0"
|
||||
openssl = "=0.10"
|
||||
mail-parser = "^0"
|
||||
matchit = "=0.7.3"
|
||||
rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored"] }
|
||||
|
||||
datafusion = "39.0.0"
|
||||
object_store = { version = "0.10.0", features = ["aws", "azure"] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
7cdaac656fde3f3e267dfb9e94dda7f3843aa44e
|
||||
51dcbf93b0d127af9f33fa346cc63fcd2475d4fa
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE kafka_trigger;
|
||||
@@ -1,69 +0,0 @@
|
||||
-- Add up migration script here
|
||||
-- Add up migration script here
|
||||
|
||||
CREATE TABLE kafka_trigger (
|
||||
path VARCHAR(255) NOT NULL,
|
||||
kafka_resource_path VARCHAR(255) NOT NULL,
|
||||
topics VARCHAR(255)[] NOT NULL,
|
||||
group_id VARCHAR(255) NOT NULL,
|
||||
script_path VARCHAR(255) NOT NULL,
|
||||
is_flow BOOLEAN NOT NULL,
|
||||
workspace_id VARCHAR(50) NOT NULL,
|
||||
edited_by VARCHAR(50) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
extra_perms JSONB NOT NULL DEFAULT '{}',
|
||||
server_id VARCHAR(50) NULL,
|
||||
last_server_ping TIMESTAMPTZ NULL,
|
||||
error TEXT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
PRIMARY KEY (path, workspace_id)
|
||||
);
|
||||
|
||||
GRANT ALL ON kafka_trigger TO windmill_user;
|
||||
GRANT ALL ON kafka_trigger TO windmill_admin;
|
||||
|
||||
ALTER TABLE kafka_trigger ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY admin_policy ON kafka_trigger FOR ALL TO windmill_admin USING (true);
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_select ON kafka_trigger FOR SELECT TO windmill_user
|
||||
USING (SPLIT_PART(kafka_trigger.path, '/', 1) = 'f' AND SPLIT_PART(kafka_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_insert ON kafka_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (SPLIT_PART(kafka_trigger.path, '/', 1) = 'f' AND SPLIT_PART(kafka_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_update ON kafka_trigger FOR UPDATE TO windmill_user
|
||||
USING (SPLIT_PART(kafka_trigger.path, '/', 1) = 'f' AND SPLIT_PART(kafka_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_delete ON kafka_trigger FOR DELETE TO windmill_user
|
||||
USING (SPLIT_PART(kafka_trigger.path, '/', 1) = 'f' AND SPLIT_PART(kafka_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_own ON kafka_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(kafka_trigger.path, '/', 1) = 'u' AND SPLIT_PART(kafka_trigger.path, '/', 2) = current_setting('session.user'));
|
||||
CREATE POLICY see_member ON kafka_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(kafka_trigger.path, '/', 1) = 'g' AND SPLIT_PART(kafka_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_select ON kafka_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
|
||||
CREATE POLICY see_extra_perms_user_insert ON kafka_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_update ON kafka_trigger FOR UPDATE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_delete ON kafka_trigger FOR DELETE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_select ON kafka_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
|
||||
CREATE POLICY see_extra_perms_groups_insert ON kafka_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_update ON kafka_trigger FOR UPDATE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_delete ON kafka_trigger FOR DELETE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
@@ -198,7 +198,6 @@ pub enum ResourceOrVariablePath {
|
||||
pub struct FileResource {
|
||||
pub resource_path: ResourceOrVariablePath,
|
||||
pub target_path: String,
|
||||
pub mode: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -438,65 +437,36 @@ fn count_consecutive_vs(s: &str) -> usize {
|
||||
|
||||
fn parse_file_resource(yaml: &Yaml) -> anyhow::Result<FileResource> {
|
||||
if let Yaml::Hash(f) = yaml {
|
||||
let target_path = f
|
||||
.get(&Yaml::String("target".to_string()))
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|x| x.to_string())
|
||||
.ok_or(anyhow!(
|
||||
"No `target` provided for the file. Please input a target path (only relative paths are allowed) where the ansible playbook can read this file.",
|
||||
))?;
|
||||
|
||||
let mut mode = None;
|
||||
if let Some(u) = f.get(&Yaml::String("mode".to_string())) {
|
||||
let mode_val: u32 = match u {
|
||||
Yaml::Integer(u) => {
|
||||
u.clone().try_into().map_err(|e| {
|
||||
anyhow!(
|
||||
"Invalid value for `mode` permissions property on targeted file to: {}, err: {e}",
|
||||
target_path
|
||||
)
|
||||
})?
|
||||
}
|
||||
Yaml::String(s) => {
|
||||
let val = if s.starts_with("0b") {
|
||||
u32::from_str_radix(&s[2..], 2)
|
||||
} else if s.starts_with("0o") {
|
||||
u32::from_str_radix(&s[2..], 8)
|
||||
} else if s.starts_with("0") {
|
||||
u32::from_str_radix(&s[1..], 8)
|
||||
} else {
|
||||
u32::from_str_radix(s, 8)
|
||||
};
|
||||
|
||||
val.map_err(|e| anyhow!("Error parsing permission mode value {s}: {e}"))?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!("Invalid field in `mode`, expected integer like 0o644"));
|
||||
}
|
||||
};
|
||||
if mode_val <= 0o777 {
|
||||
mode = Some(mode_val);
|
||||
} else {
|
||||
return Err(anyhow!("The provided value for `mode` is too big. Make sure that you are using the octal prefix (0o), e.g. `mode: 0o644`"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("resource".to_string())) {
|
||||
let target_path = f
|
||||
.get(&Yaml::String("target".to_string()))
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|x| x.to_string())
|
||||
.ok_or(anyhow!(
|
||||
"No `target` provided for file resource {}. Please input a target relative path for the ansible playbook to see this file.",
|
||||
resource_path
|
||||
))?;
|
||||
return Ok(FileResource {
|
||||
resource_path: ResourceOrVariablePath::Resource(resource_path.clone()),
|
||||
target_path,
|
||||
mode,
|
||||
});
|
||||
}
|
||||
if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("variable".to_string())) {
|
||||
let target_path = f
|
||||
.get(&Yaml::String("target".to_string()))
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|x| x.to_string())
|
||||
.ok_or(anyhow!(
|
||||
"No `target` provided for file resource {}. Please input a target relative path for the ansible playbook to see this file.",
|
||||
resource_path
|
||||
))?;
|
||||
return Ok(FileResource {
|
||||
resource_path: ResourceOrVariablePath::Variable(resource_path.clone()),
|
||||
target_path,
|
||||
mode,
|
||||
});
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"Files should have a `resource` or `variable` field that will be the contents of the local text file"
|
||||
"File resource should have a `resource` field, linking to a text file resource"
|
||||
));
|
||||
}
|
||||
return Err(anyhow!("Invalid file resource: Should be a dictionary."));
|
||||
|
||||
@@ -19,7 +19,6 @@ parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/par
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
|
||||
openidconnect = ["dep:openidconnect"]
|
||||
tantivy = ["dep:windmill-indexer"]
|
||||
kafka = ["dep:rdkafka"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -97,7 +96,6 @@ url = { workspace = true, optional = true}
|
||||
jsonwebtoken = { workspace = true }
|
||||
matchit.workspace = true
|
||||
tokio-tungstenite.workspace = true
|
||||
rdkafka = { workspace = true, optional = true }
|
||||
|
||||
pin-project.workspace = true
|
||||
http.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.426.0
|
||||
version: 1.425.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -2332,12 +2332,9 @@ paths:
|
||||
type: boolean
|
||||
websocket_used:
|
||||
type: boolean
|
||||
kafka_used:
|
||||
type: boolean
|
||||
required:
|
||||
- http_routes_used
|
||||
- websocket_used
|
||||
- kafka_used
|
||||
|
||||
/w/{workspace}/users/list:
|
||||
get:
|
||||
@@ -7688,169 +7685,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/create:
|
||||
post:
|
||||
summary: create kafka trigger
|
||||
operationId: createKafkaTrigger
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: new kafka trigger
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewKafkaTrigger"
|
||||
responses:
|
||||
"201":
|
||||
description: kafka trigger created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/update/{path}:
|
||||
post:
|
||||
summary: update kafka trigger
|
||||
operationId: updateKafkaTrigger
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
description: updated trigger
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/EditKafkaTrigger"
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger updated
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/delete/{path}:
|
||||
delete:
|
||||
summary: delete kafka trigger
|
||||
operationId: deleteKafkaTrigger
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger deleted
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/kafka_triggers/get/{path}:
|
||||
get:
|
||||
summary: get kafka trigger
|
||||
operationId: getKafkaTrigger
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/KafkaTrigger"
|
||||
|
||||
|
||||
/w/{workspace}/kafka_triggers/list:
|
||||
get:
|
||||
summary: list kafka triggers
|
||||
operationId: listKafkaTriggers
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
required: true
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: path
|
||||
description: filter by path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: is_flow
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: path_start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/KafkaTrigger"
|
||||
|
||||
|
||||
/w/{workspace}/kafka_triggers/exists/{path}:
|
||||
get:
|
||||
summary: does kafka trigger exists
|
||||
operationId: existsKafkaTrigger
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger exists
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/kafka_triggers/setenabled/{path}:
|
||||
post:
|
||||
summary: set enabled kafka trigger
|
||||
operationId: setKafkaTriggerEnabled
|
||||
tags:
|
||||
- kafka_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
description: updated kafka trigger enable
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- enabled
|
||||
responses:
|
||||
"200":
|
||||
description: kafka trigger enabled set
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/groups/list:
|
||||
get:
|
||||
@@ -8720,7 +8554,6 @@ paths:
|
||||
raw_app,
|
||||
http_trigger,
|
||||
websocket_trigger,
|
||||
kafka_trigger,
|
||||
]
|
||||
responses:
|
||||
"200":
|
||||
@@ -8759,7 +8592,6 @@ paths:
|
||||
raw_app,
|
||||
http_trigger,
|
||||
websocket_trigger,
|
||||
kafka_trigger,
|
||||
]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
@@ -8809,7 +8641,6 @@ paths:
|
||||
raw_app,
|
||||
http_trigger,
|
||||
websocket_trigger,
|
||||
kafka_trigger,
|
||||
]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
@@ -12101,8 +11932,6 @@ components:
|
||||
type: number
|
||||
websocket_count:
|
||||
type: number
|
||||
kafka_count:
|
||||
type: number
|
||||
|
||||
WebsocketTrigger:
|
||||
type: object
|
||||
@@ -12274,114 +12103,6 @@ components:
|
||||
required:
|
||||
- runnable_result
|
||||
|
||||
KafkaTrigger:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
edited_by:
|
||||
type: string
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
script_path:
|
||||
type: string
|
||||
kafka_resource_path:
|
||||
type: string
|
||||
group_id:
|
||||
type: string
|
||||
topics:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
is_flow:
|
||||
type: boolean
|
||||
extra_perms:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
email:
|
||||
type: string
|
||||
workspace_id:
|
||||
type: string
|
||||
server_id:
|
||||
type: string
|
||||
last_server_ping:
|
||||
type: string
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
- edited_at
|
||||
- script_path
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
- topics
|
||||
- extra_perms
|
||||
- is_flow
|
||||
- email
|
||||
- workspace_id
|
||||
- enabled
|
||||
|
||||
NewKafkaTrigger:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
script_path:
|
||||
type: string
|
||||
is_flow:
|
||||
type: boolean
|
||||
kafka_resource_path:
|
||||
type: string
|
||||
group_id:
|
||||
type: string
|
||||
topics:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
- is_flow
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
- topics
|
||||
|
||||
EditKafkaTrigger:
|
||||
type: object
|
||||
properties:
|
||||
kafka_resource_path:
|
||||
type: string
|
||||
group_id:
|
||||
type: string
|
||||
topics:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
script_path:
|
||||
type: string
|
||||
is_flow:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
- kafka_resource_path
|
||||
- group_id
|
||||
- topics
|
||||
- is_flow
|
||||
|
||||
Group:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -8,6 +8,11 @@ use std::collections::HashMap;
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{job_helpers_ee::{
|
||||
get_random_file_name, get_s3_resource, get_workspace_s3_resource, upload_file_internal,
|
||||
UploadFileResponse,
|
||||
}, users::fetch_api_authed_from_permissioned_as};
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
resources::get_resource_value_interpolated_internal,
|
||||
@@ -17,14 +22,6 @@ use crate::{
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{
|
||||
job_helpers_ee::{
|
||||
get_random_file_name, get_s3_resource, get_workspace_s3_resource, upload_file_internal,
|
||||
UploadFileResponse,
|
||||
},
|
||||
users::fetch_api_authed_from_permissioned_as,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Json, Path, Query},
|
||||
response::IntoResponse,
|
||||
@@ -1291,6 +1288,7 @@ async fn upload_s3_file_from_app() -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct UploadFileToS3Query {
|
||||
@@ -1355,14 +1353,9 @@ async fn upload_s3_file_from_app(
|
||||
let (username, permissioned_as, email) =
|
||||
get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?;
|
||||
|
||||
let on_behalf_authed = fetch_api_authed_from_permissioned_as(
|
||||
permissioned_as,
|
||||
email,
|
||||
&w_id,
|
||||
&db,
|
||||
Some(username),
|
||||
)
|
||||
.await?;
|
||||
let on_behalf_authed =
|
||||
fetch_api_authed_from_permissioned_as(permissioned_as, email, &w_id, &db, username)
|
||||
.await?;
|
||||
|
||||
if let Some(file_key) = query.file_key {
|
||||
// file key is provided => requires workspace, user or list policy and must match the regex
|
||||
|
||||
@@ -23,7 +23,7 @@ use windmill_common::{
|
||||
utils::{not_found_if_none, StripPath},
|
||||
};
|
||||
|
||||
const KINDS: [&str; 12] = [
|
||||
const KINDS: [&str; 10] = [
|
||||
"script",
|
||||
"group_",
|
||||
"resource",
|
||||
@@ -34,8 +34,6 @@ const KINDS: [&str; 12] = [
|
||||
"app",
|
||||
"raw_app",
|
||||
"http_trigger",
|
||||
"websocket_trigger",
|
||||
"kafka_trigger",
|
||||
];
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
|
||||
@@ -507,7 +507,7 @@ async fn get_http_route_trigger(
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override.unwrap_or("anonymous".to_string())),
|
||||
username_override.unwrap_or("anonymous".to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::db::DB;
|
||||
use axum::Router;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
}
|
||||
|
||||
pub async fn start_kafka_consumers(
|
||||
_db: DB,
|
||||
_rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
mut _killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
// implementation is not open source
|
||||
}
|
||||
@@ -68,8 +68,6 @@ mod ai;
|
||||
mod job_helpers_ee;
|
||||
pub mod job_metrics;
|
||||
pub mod jobs;
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
mod kafka_triggers_ee;
|
||||
pub mod oauth2_ee;
|
||||
mod oidc_ee;
|
||||
mod raw_apps;
|
||||
@@ -237,9 +235,6 @@ pub async fn run_server(
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "kafka")]
|
||||
// start_listening().await;
|
||||
|
||||
let job_helpers_service = {
|
||||
#[cfg(feature = "parquet")]
|
||||
{
|
||||
@@ -252,27 +247,9 @@ pub async fn run_server(
|
||||
}
|
||||
};
|
||||
|
||||
let kafka_triggers_service = {
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
kafka_triggers_ee::workspaced_service()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "kafka")))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
};
|
||||
|
||||
if !*CLOUD_HOSTED {
|
||||
let ws_killpill_rx = rx.resubscribe();
|
||||
websocket_triggers::start_websockets(db.clone(), rsmq.clone(), ws_killpill_rx).await;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "kafka"))]
|
||||
{
|
||||
let kafka_killpill_rx = rx.resubscribe();
|
||||
kafka_triggers_ee::start_kafka_consumers(db.clone(), rsmq, kafka_killpill_rx).await;
|
||||
}
|
||||
websocket_triggers::start_websockets(db.clone(), rsmq, ws_killpill_rx).await;
|
||||
}
|
||||
|
||||
// build our application with a route
|
||||
@@ -319,8 +296,7 @@ pub async fn run_server(
|
||||
.nest(
|
||||
"/websocket_triggers",
|
||||
websocket_triggers::workspaced_service(),
|
||||
)
|
||||
.nest("/kafka_triggers", kafka_triggers_service),
|
||||
),
|
||||
)
|
||||
.nest("/workspaces", workspaces::global_service())
|
||||
.nest(
|
||||
@@ -345,7 +321,10 @@ pub async fn run_server(
|
||||
"/srch/w/:workspace_id/index",
|
||||
indexer_ee::workspaced_service(),
|
||||
)
|
||||
.nest("/srch/index", indexer_ee::global_service())
|
||||
.nest(
|
||||
"/srch/index",
|
||||
indexer_ee::global_service(),
|
||||
)
|
||||
.nest("/oidc", oidc_ee::global_service())
|
||||
.nest(
|
||||
"/saml",
|
||||
|
||||
@@ -18,7 +18,6 @@ pub struct TriggersCount {
|
||||
webhook_count: i64,
|
||||
email_count: i64,
|
||||
websocket_count: i64,
|
||||
kafka_count: i64,
|
||||
}
|
||||
pub(crate) async fn get_triggers_count_internal(
|
||||
db: &DB,
|
||||
@@ -65,16 +64,6 @@ pub(crate) async fn get_triggers_count_internal(
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
let kafka_count = sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM kafka_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
|
||||
path,
|
||||
is_flow,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
let webhook_count = (if is_flow {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM token WHERE label LIKE 'webhook-%' AND workspace_id = $1 AND scopes @> ARRAY['run:flow/' || $2]::text[]",
|
||||
@@ -116,7 +105,6 @@ pub(crate) async fn get_triggers_count_internal(
|
||||
webhook_count,
|
||||
email_count,
|
||||
websocket_count,
|
||||
kafka_count,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -746,7 +746,7 @@ pub async fn fetch_api_authed(
|
||||
email: String,
|
||||
w_id: &str,
|
||||
db: &DB,
|
||||
username_override: Option<String>,
|
||||
username_override: String,
|
||||
) -> error::Result<ApiAuthed> {
|
||||
let permissioned_as = username_to_permissioned_as(username.as_str());
|
||||
fetch_api_authed_from_permissioned_as(permissioned_as, email, w_id, db, username_override).await
|
||||
@@ -757,7 +757,7 @@ pub async fn fetch_api_authed_from_permissioned_as(
|
||||
email: String,
|
||||
w_id: &str,
|
||||
db: &DB,
|
||||
username_override: Option<String>,
|
||||
username_override: String,
|
||||
) -> error::Result<ApiAuthed> {
|
||||
let authed =
|
||||
fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?;
|
||||
@@ -769,7 +769,7 @@ pub async fn fetch_api_authed_from_permissioned_as(
|
||||
groups: authed.groups,
|
||||
folders: authed.folders,
|
||||
scopes: authed.scopes,
|
||||
username_override: username_override,
|
||||
username_override: Some(username_override),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@ async fn wait_runnable_result(
|
||||
ws_trigger.email.clone(),
|
||||
&ws_trigger.workspace_id,
|
||||
&db,
|
||||
Some(username_override),
|
||||
username_override,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -773,9 +773,7 @@ async fn listen_to_websocket(
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> () {
|
||||
if let None = update_ping(&db, &ws_trigger, Some("Connecting...")).await {
|
||||
return;
|
||||
}
|
||||
update_ping(&db, &ws_trigger, Some("Connecting...")).await;
|
||||
|
||||
let url = ws_trigger.url.as_str();
|
||||
|
||||
@@ -972,7 +970,7 @@ async fn run_job(
|
||||
trigger.email.clone(),
|
||||
&trigger.workspace_id,
|
||||
db,
|
||||
Some("anonymous".to_string()),
|
||||
"anonymous".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1296,7 +1296,6 @@ async fn set_encryption_key(
|
||||
struct UsedTriggers {
|
||||
pub websocket_used: bool,
|
||||
pub http_routes_used: bool,
|
||||
pub kafka_used: bool,
|
||||
}
|
||||
|
||||
async fn get_used_triggers(
|
||||
@@ -1307,10 +1306,7 @@ async fn get_used_triggers(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let websocket_used = sqlx::query_as!(
|
||||
UsedTriggers,
|
||||
r#"SELECT
|
||||
EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as "websocket_used!",
|
||||
EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as "http_routes_used!",
|
||||
EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as "kafka_used!""#,
|
||||
r#"SELECT EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as "websocket_used!", EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as "http_routes_used!""#,
|
||||
w_id,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use anyhow::anyhow;
|
||||
use const_format::concatcp;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
@@ -202,7 +201,6 @@ pub fn write_file_at_user_defined_location(
|
||||
job_dir: &str,
|
||||
user_defined_path: &str,
|
||||
content: &str,
|
||||
mode: Option<u32>,
|
||||
) -> error::Result<PathBuf> {
|
||||
let job_dir = Path::new(job_dir);
|
||||
let user_path = PathBuf::from(user_defined_path);
|
||||
@@ -228,13 +226,6 @@ pub fn write_file_at_user_defined_location(
|
||||
}
|
||||
|
||||
let mut file = File::create(full_path)?;
|
||||
|
||||
if let Some(mode) = mode {
|
||||
let perm = std::os::unix::fs::PermissionsExt::from_mode(mode);
|
||||
file.set_permissions(perm)
|
||||
.map_err(|e| anyhow!("Failed to set permissions to {}: {e}", user_defined_path))?;
|
||||
}
|
||||
|
||||
file.write_all(content.as_bytes())?;
|
||||
file.flush()?;
|
||||
Ok(normalized_full_path)
|
||||
|
||||
@@ -548,7 +548,6 @@ async fn create_file_resources(
|
||||
.ok_or(anyhow!(
|
||||
"Invalid inventory resource, `content` field absent or invalid"
|
||||
))?,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| anyhow!("Couldn't write inventory: {}", e))?;
|
||||
|
||||
@@ -565,13 +564,8 @@ async fn create_file_resources(
|
||||
get_resource_or_variable_content(client, &file_res.resource_path, job_id.to_string())
|
||||
.await?;
|
||||
let path = file_res.target_path.clone();
|
||||
let validated_path = write_file_at_user_defined_location(
|
||||
job_dir,
|
||||
path.as_str(),
|
||||
&r,
|
||||
file_res.mode,
|
||||
)
|
||||
.map_err(|e| anyhow!("Couldn't write text file at {}: {}", path, e))?;
|
||||
let validated_path = write_file_at_user_defined_location(job_dir, path.as_str(), &r)
|
||||
.map_err(|e| anyhow!("Couldn't write text file at {}: {}", path, e))?;
|
||||
|
||||
nsjail_mounts.push(
|
||||
define_nsjail_mount(job_dir, &validated_path)
|
||||
|
||||
@@ -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.426.0";
|
||||
export const VERSION = "v1.425.1";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.426.0";
|
||||
export const VERSION = "1.425.1";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.426.0",
|
||||
"version": "1.425.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.426.0",
|
||||
"version": "1.425.1",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.32.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.426.0",
|
||||
"version": "1.425.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import FlowTimeline from './FlowTimeline.svelte'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { get, writable, type Unsubscriber, type Writable } from 'svelte/store'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
|
||||
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
|
||||
@@ -66,10 +66,7 @@
|
||||
|
||||
export let globalModuleStates: Writable<Record<string, GraphModuleState>>[]
|
||||
export let globalDurationStatuses: Writable<Record<string, DurationStatus>>[]
|
||||
export let globalRefreshes: Record<
|
||||
string,
|
||||
(loopJob: { index: number; job: string }) => Promise<void>
|
||||
> = {}
|
||||
|
||||
export let childFlow: boolean = false
|
||||
export let reducedPolling = false
|
||||
|
||||
@@ -79,6 +76,7 @@
|
||||
export let prefix: string | undefined = undefined
|
||||
export let subflowParentsGlobalModuleStates: Writable<Record<string, GraphModuleState>>[] = []
|
||||
export let subflowParentsDurationStatuses: Writable<Record<string, DurationStatus>>[] = []
|
||||
export let isForloopSelected = false
|
||||
|
||||
let jobResults: any[] =
|
||||
flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? []
|
||||
@@ -110,6 +108,16 @@
|
||||
if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) {
|
||||
newValue.type = x[key].type
|
||||
}
|
||||
|
||||
if (x[key]?.selectedForLoopSetManually) {
|
||||
newValue.selectedForLoopSetManually = true
|
||||
newValue.selectedForloopIndex = x[key].selectedForloopIndex
|
||||
newValue.selectedForloop = x[key].selectedForloop
|
||||
} else if (x[key]?.selectedForloopIndex != undefined && newValue.selectedForloopIndex == undefined) {
|
||||
newValue.selectedForloopIndex = x[key].selectedForloopIndex
|
||||
newValue.selectedForloop = x[key].selectedForloop
|
||||
}
|
||||
|
||||
x[key] = newValue
|
||||
return x
|
||||
})
|
||||
@@ -212,6 +220,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateInnerModules() {
|
||||
if ($localModuleStates) {
|
||||
innerModules.forEach((mod, i) => {
|
||||
@@ -256,6 +265,8 @@
|
||||
},
|
||||
true
|
||||
)
|
||||
} else if (isForloopSelected) {
|
||||
setModuleState(mod.id ?? '', {}, true)
|
||||
}
|
||||
if (mod.branch_chosen) {
|
||||
setModuleState(
|
||||
@@ -284,63 +295,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
let recursiveRefresh: Record<string, (boolean) => Promise<void>> = {}
|
||||
|
||||
export async function refresh(
|
||||
root: boolean,
|
||||
loopJob: { index: number; job: string } | undefined
|
||||
) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
|
||||
if (!loopJob) {
|
||||
loopJob = {
|
||||
index: $localModuleStates[modId ?? '']?.selectedForloopIndex ?? 0,
|
||||
job: $localModuleStates[modId ?? '']?.selectedForloop ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
let last = root ? undefined : flowJobIds?.flowJobs?.[flowJobIds?.flowJobs.length - 1]
|
||||
|
||||
// console.log(innerModule, modId)
|
||||
|
||||
Object.entries(recursiveRefresh).forEach(([key, v]) => {
|
||||
if (modId) {
|
||||
if ((root && key == loopJob?.job) || key == last) {
|
||||
v(false)
|
||||
}
|
||||
} else {
|
||||
v(false)
|
||||
}
|
||||
})
|
||||
|
||||
let njob = flowJobIds
|
||||
? root && modId
|
||||
? storedListJobs?.[loopJob.job]
|
||||
: storedListJobs?.[flowJobIds.length - 1]
|
||||
: job
|
||||
|
||||
if (njob) {
|
||||
dispatch('jobsLoaded', { job: njob, force: true })
|
||||
}
|
||||
}
|
||||
$: isForloopSelected && globalModuleStates && loadJobInProgress()
|
||||
|
||||
async function getNewJob(jobId: string, initialJob: Job | undefined) {
|
||||
if (jobId == initialJob?.id &&
|
||||
initialJob?.id != undefined &&
|
||||
initialJob?.type === 'CompletedJob') {
|
||||
return initialJob;
|
||||
} else {
|
||||
return await JobService.getJob({
|
||||
workspace: workspaceId ?? $workspaceStore ?? '',
|
||||
id: jobId ?? '',
|
||||
noLogs: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let errorCount = 0
|
||||
let notAnonynmous = false
|
||||
async function loadJobInProgress() {
|
||||
dispatch('start')
|
||||
if (jobId != '00000000-0000-0000-0000-000000000000') {
|
||||
try {
|
||||
const newJob =
|
||||
jobId == initialJob?.id &&
|
||||
initialJob?.id != undefined &&
|
||||
initialJob?.type === 'CompletedJob'
|
||||
? initialJob
|
||||
: await JobService.getJob({
|
||||
workspace: workspaceId ?? $workspaceStore ?? '',
|
||||
id: jobId ?? '',
|
||||
noLogs: true
|
||||
})
|
||||
if (!deepEqual(job, newJob)) {
|
||||
const newJob = await getNewJob(jobId, initialJob)
|
||||
if (!deepEqual(job, newJob) || isForloopSelected) {
|
||||
job = newJob
|
||||
job?.flow_status && updateStatus(job?.flow_status)
|
||||
dispatch('jobsLoaded', { job, force: false })
|
||||
@@ -366,6 +346,10 @@
|
||||
}
|
||||
|
||||
let destroyed = false
|
||||
|
||||
|
||||
|
||||
|
||||
async function updateJobId() {
|
||||
if (jobId !== job?.id) {
|
||||
$localModuleStates = {}
|
||||
@@ -396,30 +380,30 @@
|
||||
$localDurationStatuses = {}
|
||||
}
|
||||
await loadJobInProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: jobId && updateJobId()
|
||||
|
||||
$: isListJob = flowJobIds != undefined && Array.isArray(flowJobIds?.flowJobs)
|
||||
|
||||
$: flowJobIds?.moduleId && onFlowJobFlowStatus()
|
||||
|
||||
function onFlowJobFlowStatus() {
|
||||
if (globalRefreshes) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
if (modId) {
|
||||
globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => {
|
||||
setIteration(loopJob.index, loopJob.job, false, modId ?? '')
|
||||
refresh(true, loopJob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let forloop_selected = get(globalModuleStates?.[globalModuleStates?.length - 1])?.[flowJobIds?.moduleId ?? '']?.selectedForloop
|
||||
|
||||
let sub: Unsubscriber | undefined = undefined
|
||||
if (flowJobIds?.moduleId) {
|
||||
sub = globalModuleStates?.[globalModuleStates?.length - 1].subscribe((x) => {
|
||||
forloop_selected = x[flowJobIds?.moduleId ?? '']?.selectedForloop
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onDestroy(() => {
|
||||
destroyed = true
|
||||
timeout && clearTimeout(timeout)
|
||||
sub?.()
|
||||
|
||||
})
|
||||
|
||||
$: selected = isListJob ? 'sequence' : 'graph'
|
||||
@@ -500,46 +484,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setIteration(j: number, id: string, clicked: boolean, modId: string) {
|
||||
if (modId) {
|
||||
if (!$localModuleStates?.[modId]) {
|
||||
$localModuleStates[modId] = {
|
||||
type: 'InProgress',
|
||||
args: undefined
|
||||
}
|
||||
}
|
||||
let state = $localModuleStates?.[modId]
|
||||
|
||||
if (state) {
|
||||
if (state.selectedForloop == id && clicked) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
selectedForloop: undefined,
|
||||
selectedForloopIndex: -1
|
||||
},
|
||||
false,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
|
||||
|
||||
function setIteration(j: number, id: string, selectedManually: boolean | undefined, clicked: boolean, modId: string, isForloop: boolean) {
|
||||
if (modId) {
|
||||
globalModuleStates?.[globalModuleStates?.length - 1]?.update((topLevelModuleStates) => {
|
||||
let state = topLevelModuleStates?.[modId]
|
||||
if (state) {
|
||||
let manualOnce = state.selectedForLoopSetManually
|
||||
if (state?.selectedForloop == id && clicked) {
|
||||
topLevelModuleStates[modId] = {
|
||||
...(topLevelModuleStates[modId] ?? {}),
|
||||
selectedForloop: undefined,
|
||||
selectedForloopIndex: -1,
|
||||
selectedForLoopSetManually: false
|
||||
}
|
||||
} else if (clicked || (!manualOnce && (state == undefined || !isForloop || j >= (state.selectedForloopIndex ?? -1)) ) || selectedManually){
|
||||
let setManually = clicked || selectedManually || manualOnce
|
||||
topLevelModuleStates[modId] = {
|
||||
...(topLevelModuleStates[modId] ?? {}),
|
||||
selectedForloop: id,
|
||||
selectedForloopIndex: j
|
||||
},
|
||||
false,
|
||||
true
|
||||
)
|
||||
clicked && refresh(true, undefined)
|
||||
selectedForloopIndex: j,
|
||||
selectedForLoopSetManually: setManually ?? false
|
||||
}
|
||||
// clicked && callGlobRefresh(modId, {index: j, job: id, selectedManually: setManually ?? false})
|
||||
}
|
||||
}
|
||||
}
|
||||
return topLevelModuleStates
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function innerJobLoaded(jobLoaded: Job, j: number, clicked: boolean, force: boolean) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
if (modId) {
|
||||
setIteration(j, jobLoaded.id, clicked, modId)
|
||||
console.log(j, jobLoaded.id)
|
||||
setIteration(j, jobLoaded.id, clicked, clicked, modId, innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow')
|
||||
|
||||
if ($flowStateStore && $flowStateStore?.[modId] == undefined) {
|
||||
$flowStateStore[modId] = {
|
||||
@@ -572,48 +554,46 @@
|
||||
let job_id = jobLoaded.id
|
||||
initializeByJob(modId)
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
if ($localModuleStates[modId]?.selectedForloopIndex == j) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
started_at,
|
||||
logs: jobLoaded.logs,
|
||||
job_id,
|
||||
args: jobLoaded.args,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined
|
||||
},
|
||||
force,
|
||||
true
|
||||
)
|
||||
let v: Partial<GraphModuleState> = {
|
||||
started_at,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined
|
||||
}
|
||||
|
||||
let currentIndex = $localModuleStates[modId]?.selectedForloopIndex == j
|
||||
if (currentIndex) {
|
||||
v.logs = jobLoaded.logs
|
||||
v.args = jobLoaded.args
|
||||
v.job_id = jobLoaded.id
|
||||
}
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
if (started_at && $localModuleStates[modId]?.type != 'InProgress') {
|
||||
v.type = 'InProgress'
|
||||
}
|
||||
} else if (jobLoaded.type == 'CompletedJob') {
|
||||
v.flow_jobs_results = jobResults
|
||||
if (currentIndex) {
|
||||
v.result = jobLoaded.result
|
||||
}
|
||||
}
|
||||
setModuleState(
|
||||
modId,
|
||||
v,
|
||||
force,
|
||||
true
|
||||
)
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
|
||||
setDurationStatusByJob(modId, job_id, {
|
||||
created_at,
|
||||
started_at
|
||||
})
|
||||
} else if (jobLoaded.type == 'CompletedJob') {
|
||||
if ($localModuleStates[modId]?.selectedForloopIndex == j) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
started_at,
|
||||
args: jobLoaded.args,
|
||||
result: jobLoaded.result,
|
||||
flow_jobs_results: jobResults,
|
||||
job_id,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined,
|
||||
isListJob: true
|
||||
},
|
||||
force,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
setDurationStatusByJob(modId, job_id, {
|
||||
created_at,
|
||||
started_at,
|
||||
@@ -702,6 +682,8 @@
|
||||
|
||||
return rec(ids, undefined)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if notAnonynmous}
|
||||
@@ -826,8 +808,6 @@
|
||||
<div class="{selected != 'sequence' ? 'hidden' : ''} max-w-7xl mx-auto">
|
||||
{#if isListJob}
|
||||
{@const sliceFrom = $localDurationStatuses[flowJobIds?.moduleId ?? '']?.iteration_from ?? 0}
|
||||
{@const forloop_selected =
|
||||
$localModuleStates?.[flowJobIds?.moduleId ?? '']?.selectedForloop}
|
||||
<h3 class="text-md leading-6 font-bold text-tertiary border-b mb-4">
|
||||
Subflows ({flowJobIds?.flowJobs.length})
|
||||
</h3>
|
||||
@@ -865,20 +845,21 @@
|
||||
</Button>
|
||||
{/if}
|
||||
{#if j >= sliceFrom || forloop_selected == loopJobId}
|
||||
{@const forloopIsSelected = forloop_selected == loopJobId || (innerModule?.type != 'forloopflow' && innerModule?.type != 'whileloopflow')}
|
||||
<!-- <LogId id={loopJobId} /> -->
|
||||
<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
|
||||
<svelte:self
|
||||
bind:refresh={recursiveRefresh[loopJobId]}
|
||||
{globalRefreshes}
|
||||
{childFlow}
|
||||
job={storedListJobs[j]}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
initialJob={storedListJobs[j]}
|
||||
globalModuleStates={forloopIsSelected ? [localModuleStates, ...globalModuleStates] : []}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
{prefix}
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={forloop_selected == loopJobId && selected == 'sequence' && render}
|
||||
reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20}
|
||||
isForloopSelected={forloop_selected == loopJobId && (innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow')}
|
||||
reducedPolling={reducedPolling || (flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20)}
|
||||
{workspaceId}
|
||||
jobId={loopJobId}
|
||||
on:jobsLoaded={(e) => {
|
||||
@@ -949,8 +930,6 @@
|
||||
<!-- <LogId id={loopJobId} /> -->
|
||||
<div class="border p-6" class:hidden={retry_selected != failedRetry}>
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[failedRetry]}
|
||||
{childFlow}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
@@ -958,7 +937,7 @@
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={failedRetry == retry_selected && render}
|
||||
reducedPolling={false}
|
||||
{reducedPolling}
|
||||
{workspaceId}
|
||||
jobId={failedRetry}
|
||||
/>
|
||||
@@ -968,8 +947,6 @@
|
||||
{#if ['InProgress', 'Success', 'Failure'].includes(mod.type)}
|
||||
{#if job.raw_flow?.modules[i]?.value.type == 'flow'}
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[mod.job ?? '']}
|
||||
globalModuleStates={[]}
|
||||
globalDurationStatuses={[]}
|
||||
prefix={buildPrefix(prefix, mod.id ?? '')}
|
||||
@@ -986,6 +963,7 @@
|
||||
render={selected == 'sequence' && render}
|
||||
{workspaceId}
|
||||
jobId={mod.job}
|
||||
{reducedPolling}
|
||||
childFlow
|
||||
on:jobsLoaded={(e) => {
|
||||
let { force, job } = e.detail
|
||||
@@ -996,8 +974,7 @@
|
||||
<div class="text-secondary">no subflow (empty loop?)</div>
|
||||
{:else}
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[mod.job ?? '']}
|
||||
|
||||
{childFlow}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
@@ -1007,6 +984,7 @@
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
jobId={mod.job}
|
||||
{reducedPolling}
|
||||
innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
@@ -1090,13 +1068,21 @@
|
||||
}}
|
||||
on:selectedIteration={(e) => {
|
||||
let detail = e.detail
|
||||
if (detail.manuallySet) {
|
||||
$localModuleStates[detail.moduleId] = {
|
||||
...$localModuleStates[detail.moduleId],
|
||||
selectedForloop: detail.id,
|
||||
selectedForloopIndex: detail.index,
|
||||
selectedForLoopSetManually: true
|
||||
}
|
||||
|
||||
setModuleState(detail.moduleId, {
|
||||
selectedForloop: detail.id,
|
||||
selectedForloopIndex: detail.index
|
||||
})
|
||||
globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index })
|
||||
// console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes)
|
||||
|
||||
} else {
|
||||
$localModuleStates[detail.moduleId] = {
|
||||
...$localModuleStates[detail.moduleId],
|
||||
selectedForLoopSetManually: false
|
||||
}
|
||||
}
|
||||
}}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
ScriptService,
|
||||
HttpTriggerService,
|
||||
VariableService,
|
||||
WebsocketTriggerService,
|
||||
KafkaTriggerService
|
||||
WebsocketTriggerService
|
||||
} from '$lib/gen'
|
||||
import { superadmin, userStore, workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
@@ -38,7 +37,6 @@
|
||||
| 'raw_app'
|
||||
| 'http_trigger'
|
||||
| 'websocket_trigger'
|
||||
| 'kafka_trigger'
|
||||
let meta: Meta | undefined = undefined
|
||||
export let fullNamePlaceholder: string | undefined = undefined
|
||||
export let namePlaceholder = ''
|
||||
@@ -227,11 +225,6 @@
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'kafka_trigger') {
|
||||
return await KafkaTriggerService.existsKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
| 'raw_app'
|
||||
| 'http_trigger'
|
||||
| 'websocket_trigger'
|
||||
| 'kafka_trigger'
|
||||
let kind: Kind
|
||||
|
||||
let path: string = ''
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
|
||||
<ResolveOpen {open} on:open on:close />
|
||||
<div use:floatingRef class="w-full">
|
||||
<MenuButton class={twMerge('w-full', justifyEnd ? 'flex justify-end' : '')}>
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
<slot slot="webhooks" name="webhooks" />
|
||||
<slot slot="routes" name="routes" />
|
||||
<slot slot="websockets" name="websockets" />
|
||||
<slot slot="kafka" name="kafka" />
|
||||
<slot slot="emails" name="emails" />
|
||||
<slot slot="schedules" name="schedules" />
|
||||
<slot slot="cli" name="cli" />
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
<slot slot="webhooks" name="webhooks" />
|
||||
<slot slot="routes" name="routes" />
|
||||
<slot slot="websockets" name="websockets" />
|
||||
<slot slot="kafka" name="kafka" />
|
||||
<slot slot="emails" name="emails" />
|
||||
<slot slot="schedules" name="schedules" />
|
||||
<slot slot="cli" name="cli" />
|
||||
@@ -95,7 +94,6 @@
|
||||
<slot slot="routes" name="routes" />
|
||||
<slot slot="script" name="script" />
|
||||
<slot slot="websockets" name="websockets" />
|
||||
<slot slot="kafka" name="kafka" />
|
||||
<slot slot="emails" name="emails" />
|
||||
<slot slot="schedules" name="schedules" />
|
||||
<slot slot="cli" name="cli" />
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { CalendarCheck2, MailIcon, Route, Terminal, Webhook, Unplug } from 'lucide-svelte'
|
||||
|
||||
import HighlightTheme from '../HighlightTheme.svelte'
|
||||
import KafkaIcon from '../icons/KafkaIcon.svelte'
|
||||
|
||||
export let triggerSelected:
|
||||
| 'webhooks'
|
||||
@@ -12,7 +11,6 @@
|
||||
| 'cli'
|
||||
| 'routes'
|
||||
| 'websockets'
|
||||
| 'kafka'
|
||||
| 'scheduledPoll' = 'webhooks'
|
||||
export let simplfiedPoll: boolean = false
|
||||
</script>
|
||||
@@ -45,12 +43,6 @@
|
||||
Websockets
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="kafka">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<KafkaIcon size={12} />
|
||||
Kafka
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="emails">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<MailIcon size={12} />
|
||||
@@ -77,8 +69,6 @@
|
||||
<slot name="schedules" />
|
||||
{:else if triggerSelected === 'websockets'}
|
||||
<slot name="websockets" />
|
||||
{:else if triggerSelected === 'kafka'}
|
||||
<slot name="kafka" />
|
||||
{:else if triggerSelected === 'cli'}
|
||||
<slot name="cli" />
|
||||
{/if}
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
{#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)}
|
||||
{#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'}
|
||||
{#if preFilter !== 'workspace'}
|
||||
<div class="pb-0 text-2xs font-light text-secondary ml-2">Workspace Folders</div>
|
||||
<div class="pb-0 text-2xs font-light text-secondary ml-2">Folders</div>
|
||||
{/if}
|
||||
|
||||
{#if owners.length > 0}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Menu from '$lib/components/common/menu/MenuV2.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { ListFilter } from 'lucide-svelte'
|
||||
import { ListFilter, Lock, LockOpen } from 'lucide-svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let index: number
|
||||
export let id: string
|
||||
export let flowJobs: string[] | undefined
|
||||
export let flowJobsSuccess: (boolean | undefined)[] | undefined
|
||||
export let selected: number
|
||||
export let selectedManually: boolean | undefined
|
||||
|
||||
let filter: number | undefined = undefined
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
@@ -21,16 +22,34 @@
|
||||
filter > 0
|
||||
) {
|
||||
event.preventDefault()
|
||||
dispatch('selectedIteration', { index: filter - 1, id: flowJobs[filter - 1] })
|
||||
dispatch('selectedIteration', { index: filter - 1, id: flowJobs[filter - 1], manuallySet: true })
|
||||
}
|
||||
}
|
||||
|
||||
let buttonHover = false
|
||||
</script>
|
||||
|
||||
<Menu maxHeight={300}>
|
||||
{#if selectedManually}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div title="Iteration picked from new subflow runs automatically" on:mouseenter={() => (buttonHover = true)} on:mouseleave={() => (buttonHover = false)} on:click={(e) => {
|
||||
buttonHover = false
|
||||
dispatch('selectedIteration', { manuallySet: false })}
|
||||
}
|
||||
class="absolute top-1.5 right-12 cursor-pointer">
|
||||
{#if buttonHover}
|
||||
<LockOpen class="text-primary" size={14} />
|
||||
{:else}
|
||||
<Lock class="text-primary" size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Menu maxHeight={300}>
|
||||
<div slot="trigger">
|
||||
<button
|
||||
title="Pick an iteration"
|
||||
id={`flow-editor-iteration picker-${index}`}
|
||||
id={`flow-editor-iteration picker-${id}`}
|
||||
type="button"
|
||||
class=" text-xs bg-surface border-[1px] border-gray-300 dark:border-gray-500 focus:outline-none
|
||||
hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-sm w-[40px] gap-1 h-[20px]
|
||||
@@ -42,6 +61,7 @@
|
||||
<ListFilter size={15} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MenuItem disabled>
|
||||
<input type="number" bind:value={filter} on:keydown={onKeydown} />
|
||||
</MenuItem>
|
||||
@@ -56,7 +76,7 @@
|
||||
: ''}"
|
||||
on:pointerdown={() => {
|
||||
//close()
|
||||
dispatch('selectedIteration', { index: idx, id })
|
||||
dispatch('selectedIteration', { index: idx, id, manuallySet: true })
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -17,16 +17,14 @@
|
||||
export let insertable: boolean
|
||||
export let annotation: string | undefined = undefined
|
||||
export let bgColor: string = ''
|
||||
export let modules: FlowModule[]
|
||||
export let moving: string | undefined = undefined
|
||||
export let duration_ms: number | undefined = undefined
|
||||
|
||||
export let retries: number | undefined = undefined
|
||||
export let flowJobs:
|
||||
| { flowJobs: string[]; selected: number; flowJobsSuccess: (boolean | undefined)[] }
|
||||
| { flowJobs: string[]; selected: number; flowJobsSuccess: (boolean | undefined)[], selectedManually: boolean | undefined }
|
||||
| undefined
|
||||
|
||||
$: idx = modules.findIndex((m) => m.id === mod.id)
|
||||
|
||||
const { selectedId } = getContext<{ selectedId: Writable<string> }>('FlowGraphContext')
|
||||
const dispatch = createEventDispatcher<{
|
||||
@@ -83,13 +81,14 @@
|
||||
{#if flowJobs && !insertable && (mod.value.type === 'forloopflow' || mod.value.type === 'whileloopflow')}
|
||||
<div class="absolute right-8 z-50 -top-5">
|
||||
<FlowJobsMenu
|
||||
id={mod.id}
|
||||
on:selectedIteration={(e) => {
|
||||
dispatch('selectedIteration', e.detail)
|
||||
}}
|
||||
flowJobsSuccess={flowJobs.flowJobsSuccess}
|
||||
flowJobs={flowJobs.flowJobs}
|
||||
selected={flowJobs.selected}
|
||||
index={idx}
|
||||
selectedManually={flowJobs.selectedManually}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -53,6 +53,7 @@ export type GraphModuleState = {
|
||||
parent_module?: string
|
||||
selectedForloop?: string
|
||||
selectedForloopIndex?: number
|
||||
selectedForLoopSetManually?: boolean
|
||||
flow_jobs_success?: (boolean | undefined)[]
|
||||
flow_jobs?: string[]
|
||||
iteration_total?: number
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'branchone',
|
||||
data.flowModuleStates?.[data.id]
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte'
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { FlowModule, FlowStatusModule } from '$lib/gen'
|
||||
import { getStateColor } from '../../util'
|
||||
import type { GraphModuleState } from '../../model'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
@@ -26,8 +26,18 @@
|
||||
if (!inputJson || typeof inputJson !== 'object' || !inputJson.iter) return {}
|
||||
return { iter: inputJson.iter }
|
||||
}
|
||||
|
||||
function computeStatus(state: GraphModuleState | undefined): FlowStatusModule["type"] | undefined {
|
||||
if (state?.type == 'InProgress' || state?.type == 'Success' || state?.type == 'Failure') {
|
||||
let r = state?.flow_jobs_success?.[state?.selectedForloopIndex ?? 0]
|
||||
if (r == undefined) return 'InProgress'
|
||||
return r ? 'Success' : 'InProgress'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
<VirtualItem
|
||||
label={data.simplifiedTriggerView ? 'For each new event' : 'Do one iteration'}
|
||||
@@ -36,7 +46,7 @@
|
||||
id={data.id}
|
||||
hideId
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
|
||||
borderColor={getStateColor(computeStatus(data.flowModuleStates?.[data.id]), darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
|
||||
@@ -38,13 +38,15 @@
|
||||
? {
|
||||
flowJobs: state?.flow_jobs,
|
||||
selected: state?.selectedForloopIndex ?? -1,
|
||||
selectedManually: state?.selectedForLoopSetManually,
|
||||
flowJobsSuccess: state?.flow_jobs_success
|
||||
}
|
||||
: (undefined as any)
|
||||
|
||||
let selectedIteration = -1
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<NodeWrapper offset={data.offset} let:darkMode>
|
||||
{#if data.module.value.type == 'flow'}
|
||||
<button
|
||||
@@ -65,12 +67,11 @@
|
||||
annotation={flowJobs &&
|
||||
(data.module.value.type === 'forloopflow' || data.module.value.type === 'whileloopflow')
|
||||
? 'Iteration: ' +
|
||||
(selectedIteration >= 0 ? selectedIteration : state?.flow_jobs?.length) +
|
||||
((state?.selectedForloopIndex ?? 0) >= 0 ? (state?.selectedForloopIndex ?? 0) + 1 : state?.flow_jobs?.length) +
|
||||
'/' +
|
||||
(state?.iteration_total ?? '?')
|
||||
: ''}
|
||||
bgColor={getStateColor(type, darkMode, true, state?.skipped)}
|
||||
modules={data.modules ?? []}
|
||||
moving={data.moving}
|
||||
duration_ms={state?.duration_ms}
|
||||
retries={data.retries}
|
||||
@@ -94,7 +95,6 @@
|
||||
data.eventHandlers.select(e.detail)
|
||||
}}
|
||||
on:selectedIteration={(e) => {
|
||||
selectedIteration = e.detail.index + 1
|
||||
data.eventHandlers.selectedIteration(e.detail, data.module.id)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import { type TriggerContext } from '$lib/components/triggers'
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte'
|
||||
|
||||
const { selectedTrigger, triggersCount } = getContext<TriggerContext>('TriggerContext')
|
||||
|
||||
@@ -19,14 +18,8 @@
|
||||
export let isFlow: boolean
|
||||
export let selected: boolean
|
||||
export let showOnlyWithCount: boolean
|
||||
export let triggersToDisplay: (
|
||||
| 'webhooks'
|
||||
| 'schedules'
|
||||
| 'routes'
|
||||
| 'websockets'
|
||||
| 'kafka'
|
||||
| 'emails'
|
||||
)[] = ['webhooks', 'schedules', 'routes', 'websockets', 'kafka', 'emails']
|
||||
export let triggersToDisplay: ('webhooks' | 'schedules' | 'routes' | 'websockets' | 'emails')[] =
|
||||
['webhooks', 'schedules', 'routes', 'websockets', 'emails']
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
@@ -54,7 +47,6 @@
|
||||
schedules: { icon: Calendar, countKey: 'schedule_count' },
|
||||
routes: { icon: Route, countKey: 'http_routes_count' },
|
||||
websockets: { icon: Unplug, countKey: 'websocket_count' },
|
||||
kafka: { icon: KafkaIcon, countKey: 'kafka_count' },
|
||||
emails: { icon: Mail, countKey: 'email_count' }
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -49,7 +49,6 @@
|
||||
import { type Changelog, changelogs } from './changelogs'
|
||||
import { page } from '$app/stores'
|
||||
import SideBarNotification from './SideBarNotification.svelte'
|
||||
import KafkaIcon from '../icons/KafkaIcon.svelte'
|
||||
|
||||
export let numUnacknowledgedCriticalAlerts = 0
|
||||
|
||||
@@ -98,13 +97,6 @@
|
||||
icon: Unplug,
|
||||
disabled: $userStore?.operator,
|
||||
kind: 'ws'
|
||||
},
|
||||
{
|
||||
label: 'Kafka',
|
||||
href: '/kafka_triggers',
|
||||
icon: KafkaIcon,
|
||||
disabled: $userStore?.operator,
|
||||
kind: 'kafka'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -213,10 +205,10 @@
|
||||
action: () => {
|
||||
isCriticalAlertsUIOpen.set(true)
|
||||
},
|
||||
icon: AlertCircle,
|
||||
notificationCount: numUnacknowledgedCriticalAlerts
|
||||
}
|
||||
]
|
||||
icon: AlertCircle,
|
||||
notificationCount: numUnacknowledgedCriticalAlerts
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,14 +11,7 @@ export type ScheduleTrigger = {
|
||||
|
||||
export type TriggerContext = {
|
||||
selectedTrigger: Writable<
|
||||
| 'webhooks'
|
||||
| 'emails'
|
||||
| 'schedules'
|
||||
| 'cli'
|
||||
| 'routes'
|
||||
| 'websockets'
|
||||
| 'scheduledPoll'
|
||||
| 'kafka'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>
|
||||
primarySchedule: Writable<ScheduleTrigger | undefined | false>
|
||||
triggersCount: Writable<TriggersCount | undefined>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte'
|
||||
import KafkaTriggerEditorInner from './KafkaTriggerEditorInner.svelte'
|
||||
|
||||
let open = false
|
||||
export async function openEdit(ePath: string, isFlow: boolean) {
|
||||
open = true
|
||||
await tick()
|
||||
drawer?.openEdit(ePath, isFlow)
|
||||
}
|
||||
|
||||
export async function openNew(is_flow: boolean, initial_script_path?: string) {
|
||||
open = true
|
||||
await tick()
|
||||
drawer?.openNew(is_flow, initial_script_path)
|
||||
}
|
||||
|
||||
let drawer: KafkaTriggerEditorInner
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<KafkaTriggerEditorInner on:update bind:this={drawer} />
|
||||
{/if}
|
||||
@@ -1,310 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import Required from '$lib/components/Required.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import { KafkaTriggerService } from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, Save, X, Plus } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import Toggle from '../Toggle.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import ResourcePicker from '../ResourcePicker.svelte'
|
||||
|
||||
let drawer: Drawer
|
||||
let is_flow: boolean = false
|
||||
let initialPath = ''
|
||||
let edit = true
|
||||
let itemKind: 'flow' | 'script' = 'script'
|
||||
let script_path = ''
|
||||
let initialScriptPath = ''
|
||||
let fixedScriptPath = ''
|
||||
let path: string = ''
|
||||
let pathError = ''
|
||||
let kafka_resource_path = ''
|
||||
let group_id = ''
|
||||
let topics: string[] = ['']
|
||||
let dirtyGroupId = false
|
||||
let enabled = false
|
||||
let dirtyPath = false
|
||||
let can_write = true
|
||||
let drawerLoading = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: is_flow = itemKind === 'flow'
|
||||
|
||||
export async function openEdit(ePath: string, isFlow: boolean) {
|
||||
drawerLoading = true
|
||||
try {
|
||||
drawer?.openDrawer()
|
||||
initialPath = ePath
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
dirtyGroupId = false
|
||||
await loadTrigger()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load kafka trigger: ${err}`, true)
|
||||
} finally {
|
||||
drawerLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
export async function openNew(nis_flow: boolean, fixedScriptPath_?: string) {
|
||||
drawerLoading = true
|
||||
try {
|
||||
drawer?.openDrawer()
|
||||
is_flow = nis_flow
|
||||
edit = false
|
||||
itemKind = nis_flow ? 'flow' : 'script'
|
||||
kafka_resource_path = ''
|
||||
group_id = ''
|
||||
topics = ['']
|
||||
dirtyGroupId = false
|
||||
initialScriptPath = ''
|
||||
fixedScriptPath = fixedScriptPath_ ?? ''
|
||||
script_path = fixedScriptPath
|
||||
path = ''
|
||||
initialPath = ''
|
||||
dirtyPath = false
|
||||
} finally {
|
||||
drawerLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(): Promise<void> {
|
||||
const s = await KafkaTriggerService.getKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath
|
||||
})
|
||||
script_path = s.script_path
|
||||
initialScriptPath = s.script_path
|
||||
|
||||
is_flow = s.is_flow
|
||||
path = s.path
|
||||
kafka_resource_path = s.kafka_resource_path
|
||||
group_id = s.group_id
|
||||
topics = s.topics
|
||||
enabled = s.enabled
|
||||
|
||||
can_write = canWrite(s.path, s.extra_perms, $userStore)
|
||||
}
|
||||
|
||||
async function updateTrigger(): Promise<void> {
|
||||
if (edit) {
|
||||
await KafkaTriggerService.updateKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path,
|
||||
script_path,
|
||||
is_flow,
|
||||
kafka_resource_path,
|
||||
group_id,
|
||||
topics
|
||||
}
|
||||
})
|
||||
sendUserToast(`Kafka trigger ${path} updated`)
|
||||
} else {
|
||||
await KafkaTriggerService.createKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path,
|
||||
script_path,
|
||||
is_flow,
|
||||
enabled: true,
|
||||
kafka_resource_path,
|
||||
group_id,
|
||||
topics
|
||||
}
|
||||
})
|
||||
sendUserToast(`Kafka trigger ${path} created`)
|
||||
}
|
||||
if (!$usedTriggerKinds.includes('kafka')) {
|
||||
$usedTriggerKinds = [...$usedTriggerKinds, 'kafka']
|
||||
}
|
||||
dispatch('update')
|
||||
drawer.closeDrawer()
|
||||
}
|
||||
|
||||
$: topicsError = topics.some((b) => /[^[a-zA-Z0-9-_.]/.test(b)) ? 'Invalid topics' : ''
|
||||
$: groupIdError = /[^a-zA-Z0-9-_.]/.test(group_id) ? 'Invalid group ID' : ''
|
||||
|
||||
$: !dirtyGroupId &&
|
||||
path &&
|
||||
(group_id = `windmill_consumer-${$workspaceStore}-${path.replaceAll('/', '__')}`)
|
||||
</script>
|
||||
|
||||
<Drawer size="800px" bind:this={drawer}>
|
||||
<DrawerContent
|
||||
title={edit
|
||||
? can_write
|
||||
? `Edit kafka trigger ${initialPath}`
|
||||
: `Kafka trigger ${initialPath}`
|
||||
: 'New kafka trigger'}
|
||||
on:close={drawer.closeDrawer}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if !drawerLoading && can_write}
|
||||
{#if edit}
|
||||
<div class="mr-8 center-center -mt-1">
|
||||
<Toggle
|
||||
disabled={!can_write}
|
||||
checked={enabled}
|
||||
options={{ right: 'enable', left: 'disable' }}
|
||||
on:change={async (e) => {
|
||||
await KafkaTriggerService.setKafkaTriggerEnabled({
|
||||
path: initialPath,
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { enabled: e.detail }
|
||||
})
|
||||
sendUserToast(`${e.detail ? 'enabled' : 'disabled'} kafka trigger ${initialPath}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
startIcon={{ icon: Save }}
|
||||
disabled={pathError != '' ||
|
||||
emptyString(script_path) ||
|
||||
emptyString(kafka_resource_path) ||
|
||||
topics.length < 1 ||
|
||||
topics.some((t) => emptyString(t)) ||
|
||||
topicsError != '' ||
|
||||
emptyString(group_id) ||
|
||||
groupIdError != '' ||
|
||||
!can_write}
|
||||
on:click={updateTrigger}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{#if drawerLoading}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:else}
|
||||
<Alert title="Info" type="info">
|
||||
{#if edit}
|
||||
Changes can take up to 30 seconds to take effect.
|
||||
{:else}
|
||||
Kafka consumers can take up to 30 seconds to start.
|
||||
{/if}
|
||||
</Alert>
|
||||
<div class="flex flex-col gap-12 mt-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<Label label="Path">
|
||||
<Path
|
||||
bind:dirty={dirtyPath}
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
checkInitialPathExistence={!edit}
|
||||
namePlaceholder="kafka_trigger"
|
||||
kind="kafka_trigger"
|
||||
disabled={!can_write}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<Section label="Kafka">
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<div class="block grow w-full">
|
||||
<div class="text-secondary text-sm mb-1">
|
||||
Resource
|
||||
<Required required={true} />
|
||||
</div>
|
||||
<ResourcePicker resourceType="kafka" bind:value={kafka_resource_path} />
|
||||
</div>
|
||||
<label class="block grow w-full">
|
||||
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
|
||||
<div>
|
||||
Topics
|
||||
<Required required={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
{#each topics as v, i}
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<input type="text" bind:value={v} />
|
||||
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
topics = topics.filter((_, index) => index !== i)
|
||||
}}
|
||||
class:hidden={topics.length === 1}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="flex items-baseline">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (topics == undefined || !Array.isArray(topics)) {
|
||||
topics = []
|
||||
}
|
||||
topics = topics.concat('')
|
||||
}}
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add topic
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">
|
||||
{topicsError}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="block grow w-full">
|
||||
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
|
||||
<div>
|
||||
Group ID
|
||||
<Required required={true} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<input type="text" bind:value={group_id} on:focus={() => (dirtyGroupId = true)} />
|
||||
</div>
|
||||
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">
|
||||
{groupIdError}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section label="Runnable">
|
||||
<p class="text-xs mb-1 text-tertiary">
|
||||
Pick a script or flow to be triggered<Required required={true} />
|
||||
</p>
|
||||
<div class="flex flex-row mb-2">
|
||||
<ScriptPicker
|
||||
disabled={fixedScriptPath != '' || !can_write}
|
||||
initialPath={fixedScriptPath || initialScriptPath}
|
||||
kinds={['script']}
|
||||
allowFlow={true}
|
||||
bind:itemKind
|
||||
bind:scriptPath={script_path}
|
||||
allowRefresh
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@@ -1,108 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '../common'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { KafkaTriggerService, type KafkaTrigger } from '$lib/gen'
|
||||
import { UnplugIcon } from 'lucide-svelte'
|
||||
|
||||
import Skeleton from '../common/skeleton/Skeleton.svelte'
|
||||
import { canWrite } from '$lib/utils'
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import type { TriggerContext } from '../triggers'
|
||||
import { getContext } from 'svelte'
|
||||
import KafkaTriggerEditor from './KafkaTriggerEditor.svelte'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
|
||||
export let isFlow: boolean
|
||||
export let path: string
|
||||
export let newItem: boolean = false
|
||||
|
||||
let kafkaTriggerEditor: KafkaTriggerEditor
|
||||
|
||||
$: path && loadTriggers()
|
||||
|
||||
const { triggersCount } = getContext<TriggerContext>('TriggerContext')
|
||||
|
||||
let kafkaTriggers: (KafkaTrigger & { canWrite: boolean })[] | undefined = undefined
|
||||
export async function loadTriggers() {
|
||||
try {
|
||||
kafkaTriggers = (
|
||||
await KafkaTriggerService.listKafkaTriggers({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path,
|
||||
isFlow
|
||||
})
|
||||
).map((x) => {
|
||||
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
|
||||
})
|
||||
$triggersCount = { ...($triggersCount ?? {}), kafka_count: kafkaTriggers?.length }
|
||||
} catch (e) {
|
||||
console.error('impossible to load kafka triggers', e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<KafkaTriggerEditor
|
||||
on:update={() => {
|
||||
loadTriggers()
|
||||
}}
|
||||
bind:this={kafkaTriggerEditor}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if !newItem}
|
||||
{#if isCloudHosted()}
|
||||
<Alert title="Not compatible with multi-tenant cloud" type="warning">
|
||||
Kafka triggers are disabled in the multi-tenant cloud.
|
||||
</Alert>
|
||||
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
<Button
|
||||
on:click={() => kafkaTriggerEditor?.openNew(isFlow, path)}
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: UnplugIcon }}
|
||||
>
|
||||
New Kafka Trigger
|
||||
</Button>
|
||||
{:else}
|
||||
<Alert title="Only workspace admins can create routes" type="warning" size="xs" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if kafkaTriggers}
|
||||
{#if kafkaTriggers.length == 0}
|
||||
<div class="text-xs text-secondary"> No kafka triggers </div>
|
||||
{:else}
|
||||
<div class="flex flex-col divide-y pt-2">
|
||||
{#each kafkaTriggers as kafkaTrigger (kafkaTrigger.path)}
|
||||
<div class="grid grid-cols-5 text-2xs items-center py-2">
|
||||
<div class="col-span-2 truncate">{kafkaTrigger.path}</div>
|
||||
<div class="col-span-2 truncate">
|
||||
{kafkaTrigger.kafka_resource_path}
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
on:click={() => kafkaTriggerEditor?.openEdit(kafkaTrigger.path, isFlow)}
|
||||
class="px-2"
|
||||
>
|
||||
{#if kafkaTrigger.canWrite}
|
||||
Edit
|
||||
{:else}
|
||||
View
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Skeleton layout={[[8]]} />
|
||||
{/if}
|
||||
|
||||
{#if newItem}
|
||||
<Alert title="Triggers disabled" type="warning" size="xs">
|
||||
Deploy the {isFlow ? 'flow' : 'script'} to add kafka triggers.
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -12,7 +12,6 @@
|
||||
import type { TriggerContext } from '$lib/components/triggers'
|
||||
import WebsocketTriggersPanel from './WebsocketTriggersPanel.svelte'
|
||||
import ScheduledPollPanel from './ScheduledPollPanel.svelte'
|
||||
import KafkaTriggersPanel from './KafkaTriggersPanel.svelte'
|
||||
|
||||
export let noEditor: boolean
|
||||
export let newItem = false
|
||||
@@ -32,7 +31,6 @@
|
||||
<Tab value="schedules" selectedClass="text-primary text-sm font-semibold">Schedules</Tab>
|
||||
<Tab value="routes" selectedClass="text-primary text-sm font-semibold">Routes</Tab>
|
||||
<Tab value="websockets" selectedClass="text-primary text-sm font-semibold">Websockets</Tab>
|
||||
<Tab value="kafka" selectedClass="text-primary text-sm font-semibold">Kafka</Tab>
|
||||
<Tab value="emails" selectedClass="text-primary text-sm font-semibold">Email</Tab>
|
||||
{#if isFlow}
|
||||
<Tab value="scheduledPoll" selectedClass="text-primary text-sm font-semibold"
|
||||
@@ -77,12 +75,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'kafka'}
|
||||
<div class="p-4">
|
||||
<KafkaTriggersPanel {newItem} path={currentPath} {isFlow} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'schedules'}
|
||||
<div class="p-4">
|
||||
<RunPageSchedules
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
url_runnable_args
|
||||
}
|
||||
})
|
||||
sendUserToast(`Websocket trigger ${path} updated`)
|
||||
sendUserToast(`Route ${path} updated`)
|
||||
} else {
|
||||
await WebsocketTriggerService.createWebsocketTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -213,7 +213,7 @@
|
||||
url_runnable_args
|
||||
}
|
||||
})
|
||||
sendUserToast(`Websocket trigger ${path} created`)
|
||||
sendUserToast(`Route ${path} created`)
|
||||
}
|
||||
if (!$usedTriggerKinds.includes('ws')) {
|
||||
$usedTriggerKinds = [...$usedTriggerKinds, 'ws']
|
||||
|
||||
@@ -514,22 +514,17 @@ export async function main(approver?: string) {
|
||||
const BUN_PREPROCESSOR_MODULE_CODE = `
|
||||
export async function preprocessor(
|
||||
wm_trigger: {
|
||||
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka',
|
||||
kind: 'http' | 'email' | 'webhook' | 'websocket',
|
||||
http?: {
|
||||
route: string // The route path, e.g. "/users/:id"
|
||||
path: string // The actual path called, e.g. "/users/123"
|
||||
method: string
|
||||
params: Record<string, string> // path parameters
|
||||
query: Record<string, string> // query parameters
|
||||
params: Record<string, string>
|
||||
query: Record<string, string>
|
||||
headers: Record<string, string>
|
||||
},
|
||||
websocket?: {
|
||||
url: string // The websocket url
|
||||
},
|
||||
kafka?: {
|
||||
brokers: string[]
|
||||
topic: string
|
||||
group_id: string
|
||||
}
|
||||
},
|
||||
/* your other args */
|
||||
@@ -543,22 +538,17 @@ export async function preprocessor(
|
||||
const DENO_PREPROCESSOR_MODULE_CODE = `
|
||||
export async function preprocessor(
|
||||
wm_trigger: {
|
||||
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka',
|
||||
kind: 'http' | 'email' | 'wehbook' | 'websocket',
|
||||
http?: {
|
||||
route: string // The route path, e.g. "/users/:id"
|
||||
path: string // The actual path called, e.g. "/users/123"
|
||||
method: string
|
||||
params: Record<string, string> // path parameters
|
||||
query: Record<string, string> // query parameters
|
||||
params: Record<string, string>
|
||||
query: Record<string, string>
|
||||
headers: Record<string, string>
|
||||
},
|
||||
websocket?: {
|
||||
url: string // The websocket url
|
||||
},
|
||||
kafka?: {
|
||||
brokers: string[]
|
||||
topic: string
|
||||
group_id: string
|
||||
}
|
||||
},
|
||||
/* your other args */
|
||||
@@ -609,16 +599,10 @@ class Http(TypedDict):
|
||||
class Websocket(TypedDict):
|
||||
url: str # The websocket url
|
||||
|
||||
class Kafka(TypedDict):
|
||||
topic: str
|
||||
brokers: list[str]
|
||||
group_id: str
|
||||
|
||||
class WmTrigger(TypedDict):
|
||||
kind: Literal["http", "email", "webhook", "websocket", "kafka"]
|
||||
http: Http | None
|
||||
websocket: Websocket | None
|
||||
kafka: Kafka | None
|
||||
kind: Literal["http", "email", "webhook", "websocket"]
|
||||
http: Http | None
|
||||
websocket: Websocket | None
|
||||
|
||||
def preprocessor(
|
||||
wm_trigger: WmTrigger,
|
||||
@@ -666,7 +650,6 @@ inventory:
|
||||
# target: ./config_template.j2
|
||||
# - variable: u/user/ssh_key
|
||||
# target: ./ssh_key
|
||||
# mode: '0600'
|
||||
|
||||
# Define the arguments of the windmill script
|
||||
extra_vars:
|
||||
|
||||
@@ -190,20 +190,15 @@
|
||||
|
||||
async function loadUsedTriggerKinds() {
|
||||
let usedKinds: string[] = []
|
||||
const { http_routes_used, websocket_used, kafka_used } = await WorkspaceService.getUsedTriggers(
|
||||
{
|
||||
workspace: $workspaceStore ?? ''
|
||||
}
|
||||
)
|
||||
const { http_routes_used, websocket_used } = await WorkspaceService.getUsedTriggers({
|
||||
workspace: $workspaceStore ?? ''
|
||||
})
|
||||
if (http_routes_used) {
|
||||
usedKinds.push('http')
|
||||
}
|
||||
if (websocket_used) {
|
||||
usedKinds.push('ws')
|
||||
}
|
||||
if (kafka_used) {
|
||||
usedKinds.push('kafka')
|
||||
}
|
||||
$usedTriggerKinds = usedKinds
|
||||
}
|
||||
|
||||
@@ -286,7 +281,7 @@
|
||||
setContext('openSearchWithPrefilledText', openSearchModal)
|
||||
|
||||
$: {
|
||||
if ($enterpriseLicense && $workspaceStore && $userStore && ($superadmin || $userStore.is_admin)) {
|
||||
if ($enterpriseLicense && $workspaceStore && $superadmin !== undefined && $userStore) {
|
||||
mountModal = true
|
||||
loadCriticalAlertsMuted()
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import TriggersBadge from '$lib/components/graph/renderers/triggers/TriggersBadge.svelte'
|
||||
import WebsocketTriggersPanel from '$lib/components/triggers/WebsocketTriggersPanel.svelte'
|
||||
import KafkaTriggersPanel from '$lib/components/triggers/KafkaTriggersPanel.svelte'
|
||||
|
||||
let flow: Flow | undefined
|
||||
let can_write = false
|
||||
@@ -540,13 +539,6 @@
|
||||
<WebsocketTriggersPanel path={flow.path ?? ''} isFlow />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="kafka">
|
||||
<div class="p-2">
|
||||
<KafkaTriggersPanel path={flow.path ?? ''} isFlow />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="emails">
|
||||
<div class="p-2">
|
||||
<EmailTriggerPanel
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export function load() {
|
||||
return {
|
||||
stuff: { title: 'Kafka triggers' }
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { KafkaTriggerService, type KafkaTrigger } from '$lib/gen'
|
||||
import {
|
||||
canWrite,
|
||||
displayDate,
|
||||
getLocalSetting,
|
||||
sendUserToast,
|
||||
storeLocalSetting
|
||||
} from '$lib/utils'
|
||||
import { base } from '$app/paths'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { Alert, Button, Skeleton } from '$lib/components/common'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { Code, Eye, Pen, Plus, Share, Trash, Circle } from 'lucide-svelte'
|
||||
import { goto } from '$lib/navigation'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
|
||||
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
|
||||
import ListFilters from '$lib/components/home/ListFilters.svelte'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { setQuery } from '$lib/navigation'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import KafkaTriggerEditor from '$lib/components/triggers/KafkaTriggerEditor.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte'
|
||||
|
||||
type TriggerW = KafkaTrigger & { canWrite: boolean }
|
||||
|
||||
let triggers: TriggerW[] = []
|
||||
let shareModal: ShareModal
|
||||
let loading = true
|
||||
|
||||
async function loadTriggers(): Promise<void> {
|
||||
triggers = (await KafkaTriggerService.listKafkaTriggers({ workspace: $workspaceStore! })).map(
|
||||
(x) => {
|
||||
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
|
||||
}
|
||||
)
|
||||
loading = false
|
||||
}
|
||||
|
||||
let interval = setInterval(async () => {
|
||||
try {
|
||||
const newTriggers = await KafkaTriggerService.listKafkaTriggers({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
for (let i = 0; i < triggers.length; i++) {
|
||||
const newTrigger = newTriggers.find((x) => x.path === triggers[i].path)
|
||||
if (newTrigger) {
|
||||
triggers[i] = {
|
||||
...triggers[i],
|
||||
error: newTrigger.error,
|
||||
last_server_ping: newTrigger.last_server_ping,
|
||||
enabled: newTrigger.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(interval)
|
||||
})
|
||||
|
||||
async function setTriggerEnabled(path: string, enabled: boolean): Promise<void> {
|
||||
try {
|
||||
await KafkaTriggerService.setKafkaTriggerEnabled({
|
||||
path,
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: { enabled }
|
||||
})
|
||||
} catch (err) {
|
||||
sendUserToast(
|
||||
`Cannot ` + (enabled ? 'enable' : 'disable') + ` kafka trigger: ${err.body}`,
|
||||
true
|
||||
)
|
||||
} finally {
|
||||
loadTriggers()
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadTriggers()
|
||||
}
|
||||
}
|
||||
let kafkaTriggerEditor: KafkaTriggerEditor
|
||||
|
||||
let filteredItems: (TriggerW & { marked?: any })[] | undefined = []
|
||||
let items: typeof filteredItems | undefined = []
|
||||
let preFilteredItems: typeof filteredItems | undefined = []
|
||||
let filter = ''
|
||||
let ownerFilter: string | undefined = undefined
|
||||
let nbDisplayed = 15
|
||||
|
||||
const TRIGGER_PATH_KIND_FILTER_SETTING = 'filter_path_of'
|
||||
const FILTER_USER_FOLDER_SETTING_NAME = 'user_and_folders_only'
|
||||
let selectedFilterKind =
|
||||
(getLocalSetting(TRIGGER_PATH_KIND_FILTER_SETTING) as 'trigger' | 'script_flow') ?? 'trigger'
|
||||
let filterUserFolders = getLocalSetting(FILTER_USER_FOLDER_SETTING_NAME) == 'true'
|
||||
|
||||
$: storeLocalSetting(TRIGGER_PATH_KIND_FILTER_SETTING, selectedFilterKind)
|
||||
$: storeLocalSetting(FILTER_USER_FOLDER_SETTING_NAME, filterUserFolders ? 'true' : undefined)
|
||||
|
||||
function filterItemsPathsBaseOnUserFilters(
|
||||
item: TriggerW,
|
||||
selectedFilterKind: 'trigger' | 'script_flow',
|
||||
filterUserFolders: boolean
|
||||
) {
|
||||
if ($workspaceStore == 'admins') return true
|
||||
if (filterUserFolders) {
|
||||
if (selectedFilterKind === 'trigger') {
|
||||
return (
|
||||
!item.path.startsWith('u/') || item.path.startsWith('u/' + $userStore?.username + '/')
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
!item.script_path.startsWith('u/') ||
|
||||
item.script_path.startsWith('u/' + $userStore?.username + '/')
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
$: preFilteredItems =
|
||||
ownerFilter != undefined
|
||||
? selectedFilterKind === 'trigger'
|
||||
? triggers?.filter(
|
||||
(x) =>
|
||||
x.path.startsWith(ownerFilter + '/') &&
|
||||
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
|
||||
)
|
||||
: triggers?.filter(
|
||||
(x) =>
|
||||
x.script_path.startsWith(ownerFilter + '/') &&
|
||||
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
|
||||
)
|
||||
: triggers?.filter((x) =>
|
||||
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
|
||||
)
|
||||
|
||||
$: if ($workspaceStore) {
|
||||
ownerFilter = undefined
|
||||
}
|
||||
|
||||
$: owners =
|
||||
selectedFilterKind === 'trigger'
|
||||
? Array.from(
|
||||
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
|
||||
).sort()
|
||||
: Array.from(
|
||||
new Set(filteredItems?.map((x) => x.script_path.split('/').slice(0, 2).join('/')) ?? [])
|
||||
).sort()
|
||||
|
||||
$: items = filter !== '' ? filteredItems : preFilteredItems
|
||||
|
||||
function updateQueryFilters(selectedFilterKind, filterUserFolders) {
|
||||
setQuery(
|
||||
new URL(window.location.href),
|
||||
TRIGGER_PATH_KIND_FILTER_SETTING,
|
||||
selectedFilterKind
|
||||
).then(() => {
|
||||
setQuery(
|
||||
new URL(window.location.href),
|
||||
FILTER_USER_FOLDER_SETTING_NAME,
|
||||
String(filterUserFolders)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function loadQueryFilters() {
|
||||
let url = new URL(window.location.href)
|
||||
let queryFilterKind = url.searchParams.get(TRIGGER_PATH_KIND_FILTER_SETTING)
|
||||
let queryFilterUserFolders = url.searchParams.get(FILTER_USER_FOLDER_SETTING_NAME)
|
||||
if (queryFilterKind) {
|
||||
selectedFilterKind = queryFilterKind as 'trigger' | 'script_flow'
|
||||
}
|
||||
if (queryFilterUserFolders) {
|
||||
filterUserFolders = queryFilterUserFolders == 'true'
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadQueryFilters()
|
||||
})
|
||||
|
||||
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
|
||||
</script>
|
||||
|
||||
<KafkaTriggerEditor on:update={loadTriggers} bind:this={kafkaTriggerEditor} />
|
||||
|
||||
<SearchItems
|
||||
{filter}
|
||||
items={preFilteredItems}
|
||||
bind:filteredItems
|
||||
f={(x) => (x.summary ?? '') + ' ' + x.path + ' (' + x.script_path + ')'}
|
||||
/>
|
||||
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Kafka triggers"
|
||||
tooltip="Windmill can consume kafka events and trigger scripts or flows based on them."
|
||||
>
|
||||
<Button size="md" startIcon={{ icon: Plus }} on:click={() => kafkaTriggerEditor.openNew(false)}>
|
||||
New kafka trigger
|
||||
</Button>
|
||||
</PageHeader>
|
||||
|
||||
{#if isCloudHosted()}
|
||||
<Alert title="Not compatible with multi-tenant cloud" type="warning">
|
||||
Kafka triggers are disabled in the multi-tenant cloud.
|
||||
</Alert>
|
||||
<div class="py-4" />
|
||||
{/if}
|
||||
<div class="w-full h-full flex flex-col">
|
||||
<div class="w-full pb-4 pt-6">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search kafka triggers"
|
||||
bind:value={filter}
|
||||
class="search-item"
|
||||
/>
|
||||
<div class="flex flex-row items-center gap-2 mt-6">
|
||||
<div class="text-sm shrink-0"> Filter by path of </div>
|
||||
<ToggleButtonGroup bind:selected={selectedFilterKind}>
|
||||
<ToggleButton small value="trigger" label="Kafka Trigger" icon={KafkaIcon} />
|
||||
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
|
||||
<div class="flex flex-row items-center justify-end gap-4">
|
||||
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
|
||||
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
|
||||
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={filterUserFolders}
|
||||
options={{ right: `Only u/${$userStore.username} and f/*` }}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if loading}
|
||||
{#each new Array(6) as _}
|
||||
<Skeleton layout={[[6], 0.4]} />
|
||||
{/each}
|
||||
{:else if !triggers?.length}
|
||||
<div class="text-center text-sm text-tertiary mt-2"> No kafka triggers </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, kafka_resource_path, topics, extra_perms, canWrite, marked, error, last_server_ping, enabled } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
|
||||
|
||||
<div
|
||||
class="hover:bg-surface-hover w-full items-center px-4 py-2 gap-4 first-of-type:!border-t-0
|
||||
first-of-type:rounded-t-md last-of-type:rounded-b-md flex flex-col"
|
||||
>
|
||||
<div class="w-full flex gap-5 items-center">
|
||||
<RowIcon kind={is_flow ? 'flow' : 'script'} />
|
||||
|
||||
<a
|
||||
href="#{path}"
|
||||
on:click={() => kafkaTriggerEditor?.openEdit(path, is_flow)}
|
||||
class="min-w-0 grow hover:underline decoration-gray-400"
|
||||
>
|
||||
<div class="text-primary flex-wrap text-left text-md font-semibold mb-1 truncate">
|
||||
{#if marked}
|
||||
<span class="text-xs">
|
||||
{@html marked}
|
||||
</span>
|
||||
{:else}
|
||||
{kafka_resource_path} - {topics.join(', ')}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-secondary text-xs truncate text-left font-light">
|
||||
{path}
|
||||
</div>
|
||||
<div class="text-secondary text-xs truncate text-left font-light">
|
||||
runnable: {script_path}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="hidden lg:flex flex-row gap-1 items-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</div>
|
||||
|
||||
<div class="w-10">
|
||||
{#if (enabled && (!ping || ping.getTime() < new Date().getTime() - 15 * 1000 || error)) || (!enabled && error)}
|
||||
<Popover notClickable>
|
||||
<span class="flex h-4 w-4">
|
||||
<Circle
|
||||
class="text-red-600 animate-ping absolute inline-flex fill-current"
|
||||
size={12}
|
||||
/>
|
||||
<Circle class="text-red-600 relative inline-flex fill-current" size={12} />
|
||||
</span>
|
||||
<div slot="text">
|
||||
{#if enabled}
|
||||
Consumer is not connected{error ? ': ' + error : ''}
|
||||
{:else}
|
||||
Consumer was disabled because of an error: {error}
|
||||
{/if}
|
||||
</div>
|
||||
</Popover>
|
||||
{:else if enabled}
|
||||
<Popover notClickable>
|
||||
<span class="flex h-4 w-4">
|
||||
<Circle class="text-green-600 relative inline-flex fill-current" size={12} />
|
||||
</span>
|
||||
<div slot="text"> Consumer is connected </div>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
checked={enabled}
|
||||
disabled={!canWrite}
|
||||
on:change={(e) => {
|
||||
setTriggerEnabled(path, e.detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex gap-2 items-center justify-end">
|
||||
<Button
|
||||
on:click={() => kafkaTriggerEditor?.openEdit(path, is_flow)}
|
||||
size="xs"
|
||||
startIcon={canWrite
|
||||
? { icon: Pen }
|
||||
: {
|
||||
icon: Eye
|
||||
}}
|
||||
color="dark"
|
||||
>
|
||||
{canWrite ? 'Edit' : 'View'}
|
||||
</Button>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: `View ${is_flow ? 'Flow' : 'Script'}`,
|
||||
icon: Eye,
|
||||
action: () => {
|
||||
goto(href)
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
type: 'delete',
|
||||
icon: Trash,
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
await KafkaTriggerService.deleteKafkaTrigger({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path
|
||||
})
|
||||
loadTriggers()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: canWrite ? 'Edit' : 'View',
|
||||
icon: canWrite ? Pen : Eye,
|
||||
action: () => {
|
||||
kafkaTriggerEditor?.openEdit(path, is_flow)
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Audit logs',
|
||||
icon: Eye,
|
||||
href: `${base}/audit_logs?resource=${path}`
|
||||
},
|
||||
{
|
||||
displayName: canWrite ? 'Share' : 'See Permissions',
|
||||
icon: Share,
|
||||
action: () => {
|
||||
shareModal.openDrawer(path, 'kafka_trigger')
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex justify-between items-baseline">
|
||||
<div
|
||||
class="flex flex-wrap text-[0.7em] text-tertiary gap-1 items-center justify-end truncate pr-2"
|
||||
><div class="truncate">edited by {edited_by}</div><div class="truncate"
|
||||
>the {displayDate(edited_at)}</div
|
||||
></div
|
||||
></div
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<NoItemFound />
|
||||
{/if}
|
||||
</div>
|
||||
{#if items && items?.length > 15 && nbDisplayed < items.length}
|
||||
<span class="text-xs"
|
||||
>{nbDisplayed} items out of {items.length}
|
||||
<button class="ml-4" on:click={() => (nbDisplayed += 30)}>load 30 more</button></span
|
||||
>
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
|
||||
<ShareModal
|
||||
bind:this={shareModal}
|
||||
on:change={() => {
|
||||
loadTriggers()
|
||||
}}
|
||||
/>
|
||||
@@ -84,7 +84,6 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import TriggersBadge from '$lib/components/graph/renderers/triggers/TriggersBadge.svelte'
|
||||
import WebsocketTriggersPanel from '$lib/components/triggers/WebsocketTriggersPanel.svelte'
|
||||
import KafkaTriggersPanel from '$lib/components/triggers/KafkaTriggersPanel.svelte'
|
||||
|
||||
let script: Script | undefined
|
||||
let topHash: string | undefined
|
||||
@@ -722,11 +721,6 @@
|
||||
<WebsocketTriggersPanel path={script.path ?? ''} isFlow={false} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="kafka">
|
||||
<div class="p-2">
|
||||
<KafkaTriggersPanel path={script.path ?? ''} isFlow={false} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="emails">
|
||||
<div class="p-2">
|
||||
<EmailTriggerPanel
|
||||
|
||||
@@ -4,8 +4,8 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.426.0"
|
||||
wmill_pg = ">=1.426.0"
|
||||
wmill = ">=1.425.1"
|
||||
wmill_pg = ">=1.425.1"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.426.0
|
||||
version: 1.425.1
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.426.0'
|
||||
ModuleVersion = '1.425.1'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill-pg"
|
||||
version = "1.426.0"
|
||||
version = "1.425.1"
|
||||
description = "An extension client for the wmill client library focused on pg"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.426.0",
|
||||
"version": "1.425.1",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.426.0",
|
||||
"version": "1.425.1",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.426.0
|
||||
1.425.1
|
||||
|
||||
Reference in New Issue
Block a user