feat: SQS triggers (#5182)

* feat: first commit

* fix: npm check

* fix: openapi file

* feat: update openapi and migration

* feat: basic implementation done

* fix: fix: no used function when no feature

* feat: capture done

* Update capture.rs

* nits: change sqs trigger

* fix: make migration great again

* feat: add message attributes

* feat: nits: fix error messages, remove console.log and add try catch

* update sqs icon and ee feature for sqs_trigger

* update: change sqs name casing and added test connection button

* nits: update Icon and add create from template button

* fix: ci build and error compilation

* update migration type sqs

* update link on create from template button for sqs, add archive in workspace export and update sqlx

* fix: ci

* Update SqsTriggerEditorInner.svelte

* add link to docs, use generic function for resource and fix import error

* chore: update .github ci

* nits: remove empty

* update to match ee repo changement

* Update backend/windmill-api/src/resources.rs

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* add sqs handling for the cli and refacoring sqsEditorInner

* Update cli/sync.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: add break to switch statement for sqs case

* fix: display aws_resource_path when retrieve or create a new trigger

* rework sqs ui, fix postgres optional port

* fix: ci

* update ui for trigger

* update repo ref and specific

* feat: add ready endpoints for workers to enterprise

* update ref

* Update frontend/src/lib/script_helpers.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
dieriba
2025-02-18 23:49:45 +01:00
committed by GitHub
parent 867ce4c842
commit c1351f8155
96 changed files with 3037 additions and 461 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ jobs:
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -81,7 +81,7 @@ jobs:
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
+1 -1
View File
@@ -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,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
- name: Rename binary with corresponding architecture
run: |
+1 -1
View File
@@ -158,7 +158,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,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}
+1 -1
View File
@@ -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,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust
- name: Rename binary with corresponding architecture
run: |
@@ -19,7 +19,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n enabled = $1, \n email = $2, \n edited_by = $3, \n edited_at = now(), \n server_id = NULL, \n error = NULL\n WHERE \n path = $4 AND \n workspace_id = $5 \n RETURNING 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Bool",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "1625a84fbcf8c5f77eb0519f60d9418f85d4d3d599f4177403fad5ad99380715"
}
@@ -0,0 +1,50 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n path,\n is_flow,\n workspace_id,\n owner,\n email,\n trigger_config as \"trigger_config!: _\"\n FROM\n capture_config\n WHERE\n trigger_kind = 'sqs' AND\n last_client_ping > NOW() - INTERVAL '10 seconds' AND\n trigger_config IS NOT NULL AND\n (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "owner",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "trigger_config!: _",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "1b33393fbbc7e681b4d355f6096d982b52b48c8c3da392b14963a1ec86811546"
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n capture_config \n SET \n last_server_ping = now(), \n error = $1 \n WHERE \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'sqs' AND \n server_id = $5 AND \n last_client_ping > NOW() - INTERVAL '10 seconds' \n RETURNING 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "1cad25c24d0f80d58a50d4da923f8b0672797299545ee0754b6ad74ece92c77e"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE \n capture_config \n SET \n last_server_ping = NULL \n WHERE \n workspace_id = $1 AND \n path = $2 AND \n is_flow = $3 AND \n trigger_kind = 'sqs' AND \n server_id IS NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "20e01ecb5d4aa4c532a8f906365c776994ae2c990bb7bb307c52296b9543fbb8"
}
@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger\n SET \n last_server_ping = now(),\n error = $1\n WHERE\n workspace_id = $2\n AND path = $3\n AND server_id = $4 \n AND enabled IS TRUE\n RETURNING 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "22dcd953d900fb0ddbe2099ccee27bcf833f56475bbc1aa3c8f6d79f146f41bf"
}
@@ -0,0 +1,106 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM sqs_trigger\n WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 4,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 11,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 12,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 13,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 14,
"name": "enabled",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false
]
},
"hash": "2b6c13191484b0c664f35e2c811082b00c44fd4a7e98a11b714133674a7b6da7"
}
@@ -0,0 +1,107 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n aws_resource_path,\n message_attributes,\n queue_url,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM \n sqs_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 2,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 11,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 13,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 14,
"name": "enabled",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false
]
},
"hash": "2ef82fad8a6ccdc66228cfbce5393de351653ab9ac171fa0eea447c905440867"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n enabled = FALSE, \n error = $1, \n server_id = NULL, \n last_server_ping = NULL \n WHERE \n workspace_id = $2 AND \n path = $3\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "3f67e7cf8d9f021a075f1c88703287a9f42e252a1246a584249a224afdbbbf8a"
}
@@ -0,0 +1,24 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n server_id = $1, \n last_server_ping = now(),\n error = 'Connecting...'\n WHERE \n enabled IS TRUE \n AND workspace_id = $2 \n AND path = $3 \n AND (last_server_ping IS NULL \n OR last_server_ping < now() - INTERVAL '15 seconds'\n ) \n RETURNING true\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "5bd8ae8d694ac9f6afef762276141d95ba195f5543120e76e68f6e6715b65a71"
}
@@ -27,7 +27,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -60,7 +61,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -19,7 +19,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n capture_config \n SET \n server_id = $1,\n last_server_ping = now(), \n error = 'Connecting...' \n WHERE \n last_client_ping > NOW() - INTERVAL '10 seconds' AND \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'sqs' AND \n (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') \n RETURNING true\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text",
"Bool"
]
},
"nullable": [
null
]
},
"hash": "6b776693091191f36eaf9e35fb3cabb01481a19bef5a956dc36cac41acad0e53"
}
@@ -30,7 +30,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n capture_config \n SET \n error = $1, \n server_id = NULL, \n last_server_ping = NULL \n WHERE \n workspace_id = $2 AND \n path = $3 AND \n is_flow = $4 AND \n trigger_kind = 'sqs'\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "7e31c47e02492b74cbb5702dfc5ef9f4c8faa986843024a2cd58e212f706aae8"
}
@@ -0,0 +1,52 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\", \n \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!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\"\n ",
"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"
},
{
"ordinal": 3,
"name": "nats_used!",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "postgres_used!",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "sqs_used!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null,
null,
null,
null,
null
]
},
"hash": "93d0ee34c7b7c56ab9cae28071f8e5fdaff0e601e2760cc5653d197c51b106bb"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO sqs_trigger (\n aws_resource_path,\n queue_url,\n message_attributes,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4, \n $5, \n $6, \n $7,\n $8,\n $9,\n $10\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"TextArray",
"Varchar",
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Bool",
"Varchar"
]
},
"nullable": []
},
"hash": "9b9bc21023f10a0b4bb45879c7da8e119bffd2982b97fc900358506a0a14bbb8"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET\n last_server_ping = NULL \n WHERE \n workspace_id = $1 \n AND path = $2 \n AND server_id IS NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "a5fbef9db2308920ea26f6154f0f3490f88ced636c9ebdecba2205f964b1d0f1"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(\n SELECT \n 1 \n FROM \n sqs_trigger \n WHERE \n path = $1 AND \n workspace_id = $2\n )",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "b386d320f9fe1d569a16e6626b723b4376a93342702856da2ac70f6bbfc7933e"
}
@@ -22,7 +22,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -19,7 +19,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -15,7 +15,7 @@
]
},
"nullable": [
null
true
]
},
"hash": "ddf2eccb78a310ed00c7d8b9c3f05d394a7cbcf0038c72a78add5c7b02ef5927"
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE \n FROM \n sqs_trigger \n WHERE \n workspace_id = $1 AND \n path = $2\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "dea056c89313f4facd62cbbc9fa33ba30fa85efcc83fafe4dd7b4e535b96a8d8"
}
@@ -27,7 +27,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -35,7 +35,8 @@
"kafka",
"email",
"nats",
"postgres"
"postgres",
"sqs"
]
}
}
@@ -0,0 +1,104 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n queue_url,\n aws_resource_path,\n message_attributes,\n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled\n FROM\n sqs_trigger\n WHERE\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "queue_url",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "aws_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "message_attributes",
"type_info": "TextArray"
},
{
"ordinal": 3,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "script_path",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "is_flow",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "edited_by",
"type_info": "Varchar"
},
{
"ordinal": 8,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"name": "edited_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "server_id",
"type_info": "Varchar"
},
{
"ordinal": 11,
"name": "last_server_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 12,
"name": "extra_perms",
"type_info": "Jsonb"
},
{
"ordinal": 13,
"name": "error",
"type_info": "Text"
},
{
"ordinal": 14,
"name": "enabled",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false
]
},
"hash": "e6adaebcade2e25be800e8b888b23c94caae2421f3cae7c06d6346bd6de1d94a"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE \n sqs_trigger \n SET \n aws_resource_path = $1,\n queue_url = $2,\n message_attributes = $3, \n is_flow = $4, \n edited_by = $5, \n email = $6,\n script_path = $7,\n path = $8,\n edited_at = now(), \n error = NULL,\n server_id = NULL\n WHERE \n workspace_id = $9 AND \n path = $10\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"TextArray",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "f68d23841e0e31cdf8633aaf0f32777e04e4965682e5b37afbe84194b756d5f5"
}
+1 -1
View File
@@ -11,5 +11,5 @@
"remote.autoForwardPorts": true,
"conventionalCommits.scopes": [
"restructring triggers, decoding trigger message on work"
],
]
}
+24
View File
@@ -716,6 +716,28 @@ dependencies = [
"uuid 1.13.2",
]
[[package]]
name = "aws-sdk-sqs"
version = "1.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03247b11956444de4699fb0b84ed32989ed9530900020d7fb8983174b5b358f9"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sso"
version = "1.59.0"
@@ -10912,6 +10934,8 @@ dependencies = [
"async-stream",
"async-stripe",
"async_zip",
"aws-config",
"aws-sdk-sqs",
"axum",
"base32",
"base64 0.22.1",
+2
View File
@@ -74,6 +74,7 @@ mssql = ["windmill-worker/mssql"]
bigquery = ["windmill-worker/bigquery"]
websocket = ["windmill-api/websocket"]
postgres_trigger = ["windmill-api/postgres_trigger"]
sqs_trigger = ["windmill-api/sqs_trigger"]
python = ["windmill-worker/python"]
smtp = ["windmill-api/smtp", "windmill-common/smtp"]
csharp = ["windmill-worker/csharp"]
@@ -306,6 +307,7 @@ datafusion = "39.0.0"
object_store = { version = "0.10.0", features = ["aws", "azure"] }
openidconnect = { version = "4.0.0-rc.1" }
aws-config = "^1"
aws-sdk-sqs = "1.57.0"
aws-sdk-sts = "^1"
crc = "^3"
+1 -1
View File
@@ -1 +1 @@
5d25cf2cd15c1953794045fd7debea14a33c7519
5e7e98536a8eb632961b3b597deac95aaca1cdbe
@@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS sqs_trigger;
@@ -0,0 +1,69 @@
-- Add up migration script here
CREATE TABLE sqs_trigger(
path VARCHAR(255) NOT NULL,
queue_url VARCHAR(255) NOT NULL,
aws_resource_path VARCHAR(255) NOT NULL,
message_attributes TEXT[],
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 NULL,
error TEXT NULL,
server_id VARCHAR(50) NULL,
last_server_ping TIMESTAMPTZ NULL,
enabled BOOLEAN NOT NULL,
CONSTRAINT PK_sqs_trigger PRIMARY KEY (path,workspace_id),
CONSTRAINT fk_sqs_trigger_workspace FOREIGN KEY (workspace_id)
REFERENCES workspace(id) ON DELETE CASCADE
);
GRANT ALL ON sqs_trigger TO windmill_user;
GRANT ALL ON sqs_trigger TO windmill_admin;
ALTER TABLE sqs_trigger ENABLE ROW LEVEL SECURITY;
CREATE POLICY admin_policy ON sqs_trigger FOR ALL TO windmill_admin USING (true);
CREATE POLICY see_folder_extra_perms_user_select ON sqs_trigger FOR SELECT TO windmill_user
USING (SPLIT_PART(sqs_trigger.path, '/', 1) = 'f' AND SPLIT_PART(sqs_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_insert ON sqs_trigger FOR INSERT TO windmill_user
WITH CHECK (SPLIT_PART(sqs_trigger.path, '/', 1) = 'f' AND SPLIT_PART(sqs_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_update ON sqs_trigger FOR UPDATE TO windmill_user
USING (SPLIT_PART(sqs_trigger.path, '/', 1) = 'f' AND SPLIT_PART(sqs_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_folder_extra_perms_user_delete ON sqs_trigger FOR DELETE TO windmill_user
USING (SPLIT_PART(sqs_trigger.path, '/', 1) = 'f' AND SPLIT_PART(sqs_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
CREATE POLICY see_own ON sqs_trigger FOR ALL TO windmill_user
USING (SPLIT_PART(sqs_trigger.path, '/', 1) = 'u' AND SPLIT_PART(sqs_trigger.path, '/', 2) = current_setting('session.user'));
CREATE POLICY see_member ON sqs_trigger FOR ALL TO windmill_user
USING (SPLIT_PART(sqs_trigger.path, '/', 1) = 'g' AND SPLIT_PART(sqs_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
CREATE POLICY see_extra_perms_user_select ON sqs_trigger FOR SELECT TO windmill_user
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
CREATE POLICY see_extra_perms_user_insert ON sqs_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 sqs_trigger FOR UPDATE TO windmill_user
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_user_delete ON sqs_trigger FOR DELETE TO windmill_user
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
CREATE POLICY see_extra_perms_groups_select ON sqs_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 sqs_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 sqs_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 sqs_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));
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'sqs';
+1
View File
@@ -899,6 +899,7 @@ Windmill Community Edition {GIT_VERSION}
let metrics_f = async {
let enabled = METRICS_ENABLED.load(std::sync::atomic::Ordering::Relaxed);
#[cfg(not(all(feature = "enterprise", feature = "prometheus")))]
if enabled {
tracing::error!("Metrics are only available in the EE, ignoring...");
+4 -1
View File
@@ -29,6 +29,7 @@ oauth2 = ["dep:async-oauth2"]
http_trigger = ["dep:matchit"]
static_frontend = ["dep:rust-embed"]
postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"]
sqs_trigger = ["dep:aws-sdk-sqs", "dep:thiserror", "dep:aws-config"]
[dependencies]
windmill-queue.workspace = true
@@ -118,4 +119,6 @@ pg_escape = { workspace = true, optional = true }
byteorder = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
rust_decimal = { workspace = true, optional = true }
rust-postgres-native-tls = { workspace = true, optional = true}
rust-postgres-native-tls = { workspace = true, optional = true}
aws-sdk-sqs = { workspace = true, optional = true }
aws-config = { workspace = true, optional = true}
+294 -92
View File
@@ -2611,12 +2611,15 @@ paths:
type: boolean
postgres_used:
type: boolean
sqs_used:
type: boolean
required:
- http_routes_used
- websocket_used
- kafka_used
- nats_used
- postgres_used
- sqs_used
/w/{workspace}/users/list:
get:
summary: list users
@@ -8564,6 +8567,196 @@ paths:
schema:
type: string
/w/{workspace}/sqs_triggers/create:
post:
summary: create sqs trigger
operationId: createSqsTrigger
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: new sqs trigger
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewSqsTrigger"
responses:
"201":
description: sqs trigger created
content:
text/plain:
schema:
type: string
/w/{workspace}/sqs_triggers/update/{path}:
post:
summary: update sqs trigger
operationId: updateSqsTrigger
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
description: updated trigger
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EditSqsTrigger"
responses:
"200":
description: sqs trigger updated
content:
text/plain:
schema:
type: string
/w/{workspace}/sqs_triggers/delete/{path}:
delete:
summary: delete sqs trigger
operationId: deleteSqsTrigger
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: sqs trigger deleted
content:
text/plain:
schema:
type: string
/w/{workspace}/sqs_triggers/get/{path}:
get:
summary: get sqs trigger
operationId: getSqsTrigger
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: sqs trigger deleted
content:
application/json:
schema:
$ref: "#/components/schemas/SqsTrigger"
/w/{workspace}/sqs_triggers/list:
get:
summary: list sqs triggers
operationId: listSqsTriggers
tags:
- sqs_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: sqs trigger list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/SqsTrigger"
/w/{workspace}/sqs_triggers/exists/{path}:
get:
summary: does sqs trigger exists
operationId: existsSqsTrigger
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: sqs trigger exists
content:
application/json:
schema:
type: boolean
/w/{workspace}/sqs_triggers/setenabled/{path}:
post:
summary: set enabled sqs trigger
operationId: setSqsTriggerEnabled
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
description: updated sqs trigger enable
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
required:
- enabled
responses:
"200":
description: sqs trigger enabled set
content:
text/plain:
schema:
type: string
/w/{workspace}/sqs_triggers/test:
post:
summary: test sqs connection
operationId: testSqsConnection
tags:
- sqs_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: test sqs connection
required: true
content:
application/json:
schema:
type: object
properties:
connection:
type: object
required:
- connection
responses:
"200":
description: successfuly connected to sqs
content:
text/plain:
schema:
type: string
/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}:
get:
summary: check if postgres configuration is set to logical
@@ -9851,6 +10044,7 @@ paths:
kafka_trigger,
nats_trigger,
postgres_trigger,
sqs_trigger
]
responses:
"200":
@@ -9892,6 +10086,7 @@ paths:
kafka_trigger,
nats_trigger,
postgres_trigger,
sqs_trigger
]
requestBody:
description: acl to add
@@ -9944,6 +10139,7 @@ paths:
kafka_trigger,
nats_trigger,
postgres_trigger,
sqs_trigger
]
requestBody:
description: acl to add
@@ -13166,6 +13362,10 @@ components:
TriggerExtraProperty:
type: object
properties:
path:
type: string
script_path:
type: string
email:
type: string
extra_perms:
@@ -13179,22 +13379,23 @@ components:
edited_at:
type: string
format: date-time
is_flow:
type: boolean
required:
- path
- script_path
- email
- extra_perms
- workspace_id
- edited_by
- edited_at
- is_flow
HttpTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
path:
type: string
script_path:
type: string
route_path:
type: string
static_asset_config:
@@ -13208,8 +13409,6 @@ components:
type: string
required:
- s3
is_flow:
type: boolean
http_method:
type: string
enum:
@@ -13226,15 +13425,7 @@ components:
type: boolean
required:
- path
- edited_by
- edited_at
- script_path
- route_path
- extra_perms
- is_flow
- email
- workspace_id
- is_async
- requires_auth
- http_method
@@ -13357,20 +13548,16 @@ components:
type: number
nats_count:
type: number
sqs_count:
type: number
WebsocketTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
path:
type: string
script_path:
type: string
url:
type: string
is_flow:
type: boolean
server_id:
type: string
last_server_ping:
@@ -13401,15 +13588,7 @@ components:
type: boolean
required:
- path
- edited_by
- edited_at
- script_path
- url
- extra_perms
- is_flow
- email
- workspace_id
- enabled
- filters
- can_return_message
@@ -13519,6 +13698,88 @@ components:
required:
- runnable_result
SqsTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
queue_url:
type: string
aws_resource_path:
type: string
message_attributes:
type: array
items:
type: string
server_id:
type: string
last_server_ping:
type: string
format: date-time
error:
type: string
enabled:
type: boolean
required:
- queue_url
- aws_resource_path
- enabled
NewSqsTrigger:
type: object
properties:
queue_url:
type: string
aws_resource_path:
type: string
message_attributes:
type: array
items:
type: string
path:
type: string
script_path:
type: string
is_flow:
type: boolean
enabled:
type: boolean
required:
- queue_url
- aws_resource_path
- path
- script_path
- is_flow
EditSqsTrigger:
type: object
properties:
queue_url:
type: string
aws_resource_path:
type: string
message_attributes:
type: array
items:
type: string
path:
type: string
script_path:
type: string
is_flow:
type: boolean
enabled:
type: boolean
required:
- queue_url
- aws_resource_path
- path
- script_path
- is_flow
- enabled
Slot:
type: object
properties:
@@ -13600,12 +13861,6 @@ components:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
path:
type: string
script_path:
type: string
is_flow:
type: boolean
enabled:
type: boolean
postgres_resource_path:
@@ -13622,9 +13877,6 @@ components:
type: string
format: date-time
required:
- path
- script_path
- is_flow
- enabled
- postgres_resource_path
- replication_slot_name
@@ -13685,17 +13937,10 @@ components:
- replication_slot_name
KafkaTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
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:
@@ -13704,16 +13949,6 @@ components:
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:
@@ -13725,17 +13960,9 @@ components:
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:
@@ -13793,17 +14020,10 @@ components:
- is_flow
NatsTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
path:
type: string
edited_by:
type: string
edited_at:
type: string
format: date-time
script_path:
type: string
nats_resource_path:
type: string
use_jetstream:
@@ -13816,16 +14036,6 @@ components:
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:
@@ -13837,17 +14047,9 @@ components:
type: boolean
required:
- path
- edited_by
- edited_at
- script_path
- nats_resource_path
- use_jetstream
- subjects
- extra_perms
- is_flow
- email
- workspace_id
- enabled
NewNatsTrigger:
@@ -14867,7 +15069,7 @@ components:
CaptureTriggerKind:
type: string
enum: [webhook, http, websocket, kafka, email, nats, postgres]
enum: [webhook, http, websocket, kafka, email, nats, postgres, sqs]
Capture:
type: object
+12
View File
@@ -98,6 +98,7 @@ pub enum TriggerKind {
Kafka,
Email,
Nats,
Sqs,
Postgres,
}
@@ -110,6 +111,7 @@ impl fmt::Display for TriggerKind {
TriggerKind::Kafka => "kafka",
TriggerKind::Email => "email",
TriggerKind::Nats => "nats",
TriggerKind::Sqs => "sqs",
TriggerKind::Postgres => "postgres",
};
write!(f, "{}", s)
@@ -132,6 +134,14 @@ pub struct KafkaTriggerConfig {
pub group_id: String,
}
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
#[derive(Debug, Serialize, Deserialize)]
pub struct SqsTriggerConfig {
pub queue_url: String,
pub aws_resource_path: String,
pub message_attributes: Option<Vec<String>>
}
#[cfg(all(feature = "enterprise", feature = "nats"))]
#[derive(Serialize, Deserialize)]
pub struct NatsTriggerConfig {
@@ -171,6 +181,8 @@ enum TriggerConfig {
Postgres(PostgresTriggerConfig),
#[cfg(feature = "websocket")]
Websocket(WebsocketTriggerConfig),
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
Sqs(SqsTriggerConfig),
#[cfg(all(feature = "enterprise", feature = "kafka"))]
Kafka(KafkaTriggerConfig),
#[cfg(all(feature = "enterprise", feature = "nats"))]
@@ -39,4 +39,4 @@ pub struct KafkaTrigger {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub enabled: bool,
}
}
+55 -27
View File
@@ -105,6 +105,8 @@ mod settings;
mod slack_approvals;
#[cfg(feature = "smtp")]
mod smtp_server_ee;
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
mod sqs_triggers_ee;
mod static_assets;
mod stripe_ee;
mod teams_ee;
@@ -319,6 +321,48 @@ pub async fn run_server(
}
};
let sqs_triggers_service = {
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
{
sqs_triggers_ee::workspaced_service()
}
#[cfg(not(all(feature = "enterprise", feature = "sqs_trigger")))]
{
Router::new()
}
};
let websocket_triggers_service = {
#[cfg(feature = "websocket")]
{
websocket_triggers::workspaced_service()
}
#[cfg(not(feature = "websocket"))]
Router::new()
};
let http_triggers_service = {
#[cfg(feature = "http_trigger")]
{
http_triggers::workspaced_service()
}
#[cfg(not(feature = "http_trigger"))]
Router::new()
};
let postgres_triggers_service = {
#[cfg(feature = "postgres_trigger")]
{
postgres_triggers::workspaced_service()
}
#[cfg(not(feature = "postgres_trigger"))]
Router::new()
};
if !*CLOUD_HOSTED {
#[cfg(feature = "websocket")]
{
@@ -337,11 +381,18 @@ pub async fn run_server(
let nats_killpill_rx = rx.resubscribe();
nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx);
}
#[cfg(feature = "postgres_trigger")]
{
let db_killpill_rx = rx.resubscribe();
postgres_triggers::start_database(db.clone(), db_killpill_rx);
}
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
{
let sqs_killpill_rx = rx.resubscribe();
sqs_triggers_ee::start_sqs(db.clone(), sqs_killpill_rx);
}
}
// build our application with a route
@@ -392,35 +443,12 @@ pub async fn run_server(
.nest("/variables", variables::workspaced_service())
.nest("/workspaces", workspaces::workspaced_service())
.nest("/oidc", oidc_ee::workspaced_service())
.nest("/http_triggers", {
#[cfg(feature = "http_trigger")]
{
http_triggers::workspaced_service()
}
#[cfg(not(feature = "http_trigger"))]
Router::new()
})
.nest("/websocket_triggers", {
#[cfg(feature = "websocket")]
{
websocket_triggers::workspaced_service()
}
#[cfg(not(feature = "websocket"))]
Router::new()
})
.nest("/http_triggers", http_triggers_service)
.nest("/websocket_triggers", websocket_triggers_service)
.nest("/kafka_triggers", kafka_triggers_service)
.nest("/nats_triggers", nats_triggers_service)
.nest("/postgres_triggers", {
#[cfg(feature = "postgres_trigger")]
{
postgres_triggers::workspaced_service()
}
#[cfg(not(feature = "postgres_trigger"))]
Router::new()
}),
.nest("/sqs_triggers", sqs_triggers_service)
.nest("/postgres_triggers", postgres_triggers_service),
)
.nest("/workspaces", workspaces::global_service())
.nest(
+1 -1
View File
@@ -40,4 +40,4 @@ pub struct NatsTrigger {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub enabled: bool,
}
}
@@ -36,11 +36,11 @@ use super::{
use lazy_static::lazy_static;
#[derive(FromRow, Serialize, Deserialize, Debug)]
pub struct Database {
pub struct Postgres {
pub user: String,
pub password: String,
pub host: String,
pub port: u16,
pub port: Option<u16>,
pub dbname: String,
#[serde(default)]
pub sslmode: String,
@@ -113,7 +113,6 @@ pub struct TestPostgres {
pub postgres_resource_path: String,
}
pub async fn test_postgres_connection(
authed: ApiAuthed,
Extension(db): Extension<DB>,
@@ -706,7 +705,7 @@ pub async fn get_publication_info(
let publication_data =
get_publication_scope_and_transaction(&mut connection, &publication_name).await;
let (all_table, transaction_to_track) = match publication_data {
let (all_table, transaction_to_track) = match publication_data {
Ok(pub_data) => pub_data,
Err(Error::SqlErr { error: sqlx::Error::RowNotFound, .. }) => {
return Err(Error::NotFound(
@@ -1457,4 +1456,4 @@ pub async fn is_database_in_logical_level(
};
Ok(Json(is_logical))
}
}
@@ -1,7 +1,7 @@
use crate::{
db::{ApiAuthed, DB},
jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery},
resources::get_resource_value_interpolated_internal,
resources::try_get_resource_from_db_as,
users::fetch_api_authed,
};
use chrono::Utc;
@@ -20,18 +20,17 @@ use axum::{
routing::{delete, get, post},
Router,
};
pub use handler::PostgresTrigger;
use handler::{
alter_publication, create_postgres_trigger, create_publication, create_slot,
create_template_script, delete_postgres_trigger, delete_publication, drop_slot_name,
exists_postgres_trigger, get_postgres_trigger, get_publication_info, get_template_script,
is_database_in_logical_level, list_database_publication, list_postgres_triggers,
list_slot_name, set_enabled, test_postgres_connection, update_postgres_trigger, Database,
list_slot_name, set_enabled, test_postgres_connection, update_postgres_trigger, Postgres,
Relations,
};
pub use handler::PostgresTrigger;
use windmill_common::{db::UserDB, error::Error, utils::StripPath};
use windmill_queue::PushArgsOwned;
mod bool;
mod converter;
mod handler;
@@ -55,13 +54,15 @@ pub async fn get_database_connection(
postgres_resource_path: &str,
w_id: &str,
) -> std::result::Result<PgConnection, windmill_common::error::Error> {
let database = get_database_resource(authed, user_db, db, postgres_resource_path, w_id).await?;
let database =
try_get_resource_from_db_as::<Postgres>(authed, user_db, db, postgres_resource_path, w_id)
.await?;
Ok(get_raw_postgres_connection(&database).await?)
}
pub async fn get_raw_postgres_connection(
db: &Database,
db: &Postgres,
) -> std::result::Result<PgConnection, Error> {
let options = {
let sslmode = if !db.sslmode.is_empty() {
@@ -69,12 +70,19 @@ pub async fn get_raw_postgres_connection(
} else {
PgSslMode::Prefer
};
let options = PgConnectOptions::new()
.host(&db.host)
.database(&db.dbname)
.port(db.port)
.ssl_mode(sslmode)
.username(&db.user);
let options = {
let inner_options = PgConnectOptions::new()
.host(&db.host)
.database(&db.dbname)
.ssl_mode(sslmode)
.username(&db.user);
if let Some(port) = db.port {
inner_options.port(port)
} else {
inner_options
}
};
let options = if !db.root_certificate_pem.is_empty() {
options.ssl_root_cert_from_pem(db.root_certificate_pem.as_bytes().to_vec())
@@ -202,39 +210,6 @@ pub fn generate_random_string() -> String {
format!("{}_{}", timestamp, random_part)
}
pub async fn get_database_resource(
authed: ApiAuthed,
user_db: Option<UserDB>,
db: &DB,
database_resource_path: &str,
w_id: &str,
) -> Result<Database, Error> {
let resource = get_resource_value_interpolated_internal(
&authed,
user_db,
&db,
&w_id,
&database_resource_path,
None,
"",
)
.await
.map_err(|_| Error::NotFound("Database resource do not exist".to_string()))?;
let resource = match resource {
Some(resource) => serde_json::from_value::<Database>(resource)?,
None => {
return {
Err(Error::NotFound(
"Database resource do not exist".to_string(),
))
}
}
};
Ok(resource)
}
fn publication_service() -> Router {
Router::new()
.route("/get/:publication_name/*path", get(get_publication_info))
@@ -4,7 +4,6 @@ use crate::{
capture::{insert_capture_payload, PostgresTriggerConfig, TriggerKind},
db::{ApiAuthed, DB},
postgres_triggers::{
get_database_resource,
relation::RelationConverter,
replication_message::{
LogicalReplicationMessage::{Begin, Commit, Delete, Insert, Relation, Type, Update},
@@ -12,7 +11,7 @@ use crate::{
},
run_job,
},
users::fetch_api_authed,
users::fetch_api_authed, resources::try_get_resource_from_db_as,
};
use bytes::{BufMut, Bytes, BytesMut};
use chrono::TimeZone;
@@ -33,7 +32,7 @@ use windmill_queue::PushArgsOwned;
use super::{
drop_logical_replication_slot_query, drop_publication_query, get_database_connection,
handler::{Database, PostgresTrigger},
handler::{Postgres, PostgresTrigger},
replication_message::PrimaryKeepAliveBody,
ERROR_PUBLICATION_NAME_NOT_EXISTS, ERROR_REPLICATION_SLOT_NOT_EXISTS,
};
@@ -80,7 +79,7 @@ enum Error {
pub struct PostgresSimpleClient(Client);
impl PostgresSimpleClient {
async fn new(database: &Database) -> Result<Self, Error> {
async fn new(database: &Postgres) -> Result<Self, Error> {
let ssl_mode = match database.sslmode.as_ref() {
"disable" => SslMode::Disable,
"" | "prefer" | "allow" => SslMode::Prefer,
@@ -98,11 +97,14 @@ impl PostgresSimpleClient {
config
.dbname(&database.dbname)
.host(&database.host)
.port(database.port)
.user(&database.user)
.ssl_mode(ssl_mode)
.replication_mode(rust_postgres::config::ReplicationMode::Logical);
if let Some(port) = database.port {
config.port(port);
};
if !database.password.is_empty() {
config.password(&database.password);
}
@@ -451,7 +453,7 @@ impl PostgresConfig {
PostgresConfig::Capture(capture) => capture.fetch_authed(db).await?,
};
let database = get_database_resource(
let database = try_get_resource_from_db_as::<Postgres>(
authed,
Some(UserDB::new(db.clone())),
&db,
+44 -1
View File
@@ -515,7 +515,9 @@ pub async fn transform_json_value<'c>(
Value::String(y) if y.starts_with("$res:") => {
let path = y.strip_prefix("$res:").unwrap();
if path.split("/").count() < 2 {
return Err(Error::internal_err(format!("Invalid resource path: {path}")));
return Err(Error::internal_err(format!(
"Invalid resource path: {path}"
)));
}
let mut tx: Transaction<'_, Postgres> =
authed_transaction_or_default(authed, user_db.clone(), db).await?;
@@ -1205,3 +1207,44 @@ async fn update_resource_type(
Ok(format!("resource_type {} updated", name))
}
#[cfg(any(
feature = "postgres_trigger",
all(feature = "sqs_trigger", feature = "enterprise")
))]
pub async fn try_get_resource_from_db_as<T>(
authed: ApiAuthed,
user_db: Option<UserDB>,
db: &DB,
resource_path: &str,
w_id: &str,
) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let resource = get_resource_value_interpolated_internal(
&authed,
user_db,
&db,
&w_id,
&resource_path,
None,
"",
)
.await?;
let resource = match resource {
Some(resource) => serde_json::from_value::<T>(resource)
.map_err(|e| Error::SerdeJson { error: e, location: "resources.rs".to_string() })?,
None => {
return {
Err(Error::NotFound(format!(
"resource at path :{} do not exist",
&resource_path
)))
}
}
};
Ok(resource)
}
@@ -0,0 +1,31 @@
use crate::db::DB;
use axum::Router;
use serde::{Deserialize, Serialize};
pub fn workspaced_service() -> Router {
Router::new()
}
pub fn start_sqs(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () {
// implementation is not open source
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SqsTrigger {
pub queue_url: String,
pub aws_resource_path: String,
pub message_attributes: Option<Vec<String>>,
pub path: String,
pub script_path: String,
pub is_flow: bool,
pub workspace_id: String,
pub edited_by: String,
pub email: String,
pub edited_at: chrono::DateTime<chrono::Utc>,
pub extra_perms: Option<serde_json::Value>,
pub error: Option<String>,
pub server_id: Option<String>,
pub last_server_ping: Option<chrono::DateTime<chrono::Utc>>,
pub enabled: bool,
}
+1 -1
View File
@@ -31,9 +31,9 @@ use windmill_common::{
};
use lazy_static::lazy_static;
use windmill_common::variables::{decrypt, encrypt};
use serde::Deserialize;
use sqlx::{Postgres, Transaction};
use windmill_common::variables::{decrypt, encrypt};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
lazy_static! {
+3 -1
View File
@@ -1362,6 +1362,7 @@ struct UsedTriggers {
pub kafka_used: bool,
pub nats_used: bool,
pub postgres_used: bool,
pub sqs_used: bool
}
async fn get_used_triggers(
@@ -1380,7 +1381,8 @@ async fn get_used_triggers(
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!",
EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as "nats_used!",
EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS "postgres_used!"
EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS "postgres_used!",
EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS "sqs_used!"
"#,
w_id
)
@@ -594,6 +594,28 @@ pub(crate) async fn tarball_workspace(
}
}
#[cfg(all(feature = "enterprise", feature = "sqs_trigger"))]
{
let sqs_triggers = sqlx::query_as!(
crate::sqs_triggers_ee::SqsTrigger,
"SELECT * FROM sqs_trigger
WHERE workspace_id = $1",
&w_id
)
.fetch_all(&mut *tx)
.await?;
for trigger in sqs_triggers {
let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap();
archive
.write_to_archive(
&trigger_str,
&format!("{}.sqs_trigger.json", trigger.path),
)
.await?;
}
}
#[cfg(all(feature = "enterprise", feature = "nats"))]
{
let nats_triggers = sqlx::query_as!(
+1
View File
@@ -352,3 +352,4 @@ pub async fn get_reserved_variables(
is_custom: true,
})).collect()
}
+1 -1
View File
@@ -54,7 +54,7 @@ export const OpenAPI: OpenAPIConfig = {
PASSWORD: undefined,
TOKEN: getEnv("WM_TOKEN"),
USERNAME: undefined,
VERSION: '1.457.1',
VERSION: '1.460.1',
WITH_CREDENTIALS: true,
interceptors: {
request: new Interceptors(),
+173 -1
View File
File diff suppressed because one or more lines are too long
+142 -35
View File
@@ -555,6 +555,8 @@ export type EditSchedule = {
};
export type TriggerExtraProperty = {
path: string;
script_path: string;
email: string;
extra_perms: {
[key: string]: (boolean);
@@ -562,18 +564,16 @@ export type TriggerExtraProperty = {
workspace_id: string;
edited_by: string;
edited_at: string;
is_flow: boolean;
};
export type HttpTrigger = TriggerExtraProperty & {
path: string;
script_path: string;
route_path: string;
static_asset_config?: {
s3: string;
storage?: string;
filename?: string;
};
is_flow: boolean;
http_method: 'get' | 'post' | 'put' | 'delete' | 'patch';
is_async: boolean;
requires_auth: boolean;
@@ -626,13 +626,11 @@ export type TriggersCount = {
postgres_count?: number;
kafka_count?: number;
nats_count?: number;
sqs_count?: number;
};
export type WebsocketTrigger = TriggerExtraProperty & {
path: string;
script_path: string;
url: string;
is_flow: boolean;
server_id?: string;
last_server_ping?: string;
error?: string;
@@ -685,6 +683,36 @@ export type WebsocketTriggerInitialMessage = {
};
};
export type SqsTrigger = TriggerExtraProperty & {
queue_url: string;
aws_resource_path: string;
message_attributes?: Array<(string)>;
server_id?: string;
last_server_ping?: string;
error?: string;
enabled: boolean;
};
export type NewSqsTrigger = {
queue_url: string;
aws_resource_path: string;
message_attributes?: Array<(string)>;
path: string;
script_path: string;
is_flow: boolean;
enabled?: boolean;
};
export type EditSqsTrigger = {
queue_url: string;
aws_resource_path: string;
message_attributes?: Array<(string)>;
path: string;
script_path: string;
is_flow: boolean;
enabled: boolean;
};
export type Slot = {
name?: string;
};
@@ -719,9 +747,6 @@ export type TemplateScript = {
};
export type PostgresTrigger = TriggerExtraProperty & {
path: string;
script_path: string;
is_flow: boolean;
enabled: boolean;
postgres_resource_path: string;
publication_name: string;
@@ -753,20 +778,10 @@ export type EditPostgresTrigger = {
publication?: PublicationData;
};
export type KafkaTrigger = {
path: string;
edited_by: string;
edited_at: string;
script_path: string;
export type KafkaTrigger = TriggerExtraProperty & {
kafka_resource_path: string;
group_id: string;
topics: Array<(string)>;
is_flow: boolean;
extra_perms: {
[key: string]: (boolean);
};
email: string;
workspace_id: string;
server_id?: string;
last_server_ping?: string;
error?: string;
@@ -792,22 +807,12 @@ export type EditKafkaTrigger = {
is_flow: boolean;
};
export type NatsTrigger = {
path: string;
edited_by: string;
edited_at: string;
script_path: string;
export type NatsTrigger = TriggerExtraProperty & {
nats_resource_path: string;
use_jetstream: boolean;
stream_name?: string;
consumer_name?: string;
subjects: Array<(string)>;
is_flow: boolean;
extra_perms: {
[key: string]: (boolean);
};
email: string;
workspace_id: string;
server_id?: string;
last_server_ping?: string;
error?: string;
@@ -1282,7 +1287,7 @@ export type CriticalAlert = {
workspace_id?: (string) | null;
};
export type CaptureTriggerKind = 'webhook' | 'http' | 'websocket' | 'kafka' | 'email' | 'nats';
export type CaptureTriggerKind = 'webhook' | 'http' | 'websocket' | 'kafka' | 'email' | 'nats' | 'postgres' | 'sqs';
export type Capture = {
trigger_kind: CaptureTriggerKind;
@@ -2751,6 +2756,7 @@ export type GetUsedTriggersResponse = ({
kafka_used: boolean;
nats_used: boolean;
postgres_used: boolean;
sqs_used: boolean;
});
export type ListUsersData = {
@@ -5850,6 +5856,95 @@ export type TestNatsConnectionData = {
export type TestNatsConnectionResponse = (string);
export type CreateSqsTriggerData = {
/**
* new sqs trigger
*/
requestBody: NewSqsTrigger;
workspace: string;
};
export type CreateSqsTriggerResponse = (string);
export type UpdateSqsTriggerData = {
path: string;
/**
* updated trigger
*/
requestBody: EditSqsTrigger;
workspace: string;
};
export type UpdateSqsTriggerResponse = (string);
export type DeleteSqsTriggerData = {
path: string;
workspace: string;
};
export type DeleteSqsTriggerResponse = (string);
export type GetSqsTriggerData = {
path: string;
workspace: string;
};
export type GetSqsTriggerResponse = (SqsTrigger);
export type ListSqsTriggersData = {
isFlow?: boolean;
/**
* which page to return (start at 1, default 1)
*/
page?: number;
/**
* filter by path
*/
path?: string;
pathStart?: string;
/**
* number of items to return for a given page (default 30, max 100)
*/
perPage?: number;
workspace: string;
};
export type ListSqsTriggersResponse = (Array<SqsTrigger>);
export type ExistsSqsTriggerData = {
path: string;
workspace: string;
};
export type ExistsSqsTriggerResponse = (boolean);
export type SetSqsTriggerEnabledData = {
path: string;
/**
* updated sqs trigger enable
*/
requestBody: {
enabled: boolean;
};
workspace: string;
};
export type SetSqsTriggerEnabledResponse = (string);
export type TestSqsConnectionData = {
/**
* test sqs connection
*/
requestBody: {
connection: {
[key: string]: unknown;
};
};
workspace: string;
};
export type TestSqsConnectionResponse = (string);
export type IsValidPostgresConfigurationData = {
path: string;
workspace: string;
@@ -6025,6 +6120,18 @@ export type SetPostgresTriggerEnabledData = {
export type SetPostgresTriggerEnabledResponse = (string);
export type TestPostgresConnectionData = {
/**
* test postgres connection
*/
requestBody: {
database: string;
};
workspace: string;
};
export type TestPostgresConnectionResponse = (string);
export type ListInstanceGroupsResponse = (Array<InstanceGroup>);
export type GetInstanceGroupData = {
@@ -6372,7 +6479,7 @@ export type ListAutoscalingEventsData = {
export type ListAutoscalingEventsResponse = (Array<AutoscalingEvent>);
export type GetGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger' | 'sqs_trigger';
path: string;
workspace: string;
};
@@ -6382,7 +6489,7 @@ export type GetGranularAclsResponse = ({
});
export type AddGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger' | 'sqs_trigger';
path: string;
/**
* acl to add
@@ -6397,7 +6504,7 @@ export type AddGranularAclsData = {
export type AddGranularAclsResponse = (string);
export type RemoveGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger' | 'kafka_trigger' | 'nats_trigger' | 'postgres_trigger' | 'sqs_trigger';
path: string;
/**
* acl to add
+10 -2
View File
@@ -640,7 +640,8 @@ export async function elementsToMap(
path.endsWith(".websocket_trigger" + ext) ||
path.endsWith(".kafka_trigger" + ext) ||
path.endsWith(".nats_trigger" + ext) ||
path.endsWith(".postgres_trigger" + ext))
path.endsWith(".postgres_trigger" + ext) ||
path.endsWith(".sqs_trigger" + ext))
)
continue;
if (!skips.includeUsers && path.endsWith(".user" + ext)) continue;
@@ -885,7 +886,8 @@ function getOrderFromPath(p: string) {
typ == "websocket_trigger" ||
typ == "kafka_trigger" ||
typ == "nats_trigger" ||
typ == "postgres_trigger"
typ == "postgres_trigger" ||
typ == "sqs_trigger"
) {
return 8;
} else if (typ == "variable") {
@@ -1719,6 +1721,12 @@ export async function push(opts: GlobalOptions & SyncOptions) {
path: removeSuffix(target, ".postgres_trigger.json"),
});
break;
case "sqs_trigger":
await wmill.deleteSqsTrigger({
workspace: workspaceId,
path: removeSuffix(target, ".sqs_trigger.json"),
});
break;
case "variable":
await wmill.deleteVariable({
workspace: workspaceId,
+10 -1
View File
@@ -4,6 +4,7 @@ import {
KafkaTrigger,
NatsTrigger,
PostgresTrigger,
SqsTrigger,
WebsocketTrigger,
} from "./gen/types.gen.ts";
import { colors, Command, log, SEP, Table } from "./deps.ts";
@@ -23,6 +24,7 @@ type Trigger = {
kafka: KafkaTrigger;
nats: NatsTrigger;
postgres: PostgresTrigger;
sqs: SqsTrigger;
};
type TriggerFile<K extends TriggerType> = Omit<
@@ -54,6 +56,7 @@ async function getTrigger<K extends TriggerType>(
kafka: wmill.getKafkaTrigger,
nats: wmill.getNatsTrigger,
postgres: wmill.getPostgresTrigger,
sqs: wmill.getSqsTrigger,
};
const triggerFunction = triggerFunctions[triggerType];
@@ -79,6 +82,7 @@ async function updateTrigger<K extends TriggerType>(
kafka: wmill.updateKafkaTrigger,
nats: wmill.updateNatsTrigger,
postgres: wmill.updatePostgresTrigger,
sqs: wmill.updateSqsTrigger,
};
const triggerFunction = triggerFunctions[triggerType];
await triggerFunction({ workspace, path, requestBody: trigger });
@@ -102,6 +106,7 @@ async function createTrigger<K extends TriggerType>(
kafka: wmill.createKafkaTrigger,
nats: wmill.createNatsTrigger,
postgres: wmill.createPostgresTrigger,
sqs: wmill.createSqsTrigger,
};
const triggerFunction = triggerFunctions[triggerType];
await triggerFunction({ workspace, path, requestBody: trigger });
@@ -175,6 +180,9 @@ async function list(opts: GlobalOptions) {
const postgresTriggers = await wmill.listPostgresTriggers({
workspace: workspace.workspaceId,
});
const sqsTriggers = await wmill.listSqsTriggers({
workspace: workspace.workspaceId,
});
const triggers = [
...httpTriggers.map((x) => ({ path: x.path, kind: "http" })),
@@ -182,6 +190,7 @@ async function list(opts: GlobalOptions) {
...kafkaTriggers.map((x) => ({ path: x.path, kind: "kafka" })),
...natsTriggers.map((x) => ({ path: x.path, kind: "nats" })),
...postgresTriggers.map((x) => ({ path: x.path, kind: "postgres" })),
...sqsTriggers.map((x) => ({ path: x.path, kind: "sqs" })),
];
new Table()
@@ -195,7 +204,7 @@ async function list(opts: GlobalOptions) {
function checkIfValidTrigger(kind: string | undefined): kind is TriggerType {
if (
kind &&
["http", "websocket", "kafka", "nats", "postgres"].includes(kind)
["http", "websocket", "kafka", "nats", "postgres", "sqs"].includes(kind)
) {
return true;
} else {
+4
View File
@@ -150,6 +150,8 @@ export async function pushObj(
await pushTrigger("nats", workspace, p, befObj, newObj);
} else if (typeEnding === "postgres_trigger") {
await pushTrigger("postgres", workspace, p, befObj, newObj);
} else if (typeEnding === "sqs_trigger") {
await pushTrigger("sqs", workspace, p, befObj, newObj);
} else if (typeEnding === "user") {
await pushWorkspaceUser(workspace, p, befObj, newObj);
} else if (typeEnding === "group") {
@@ -197,6 +199,7 @@ export function getTypeStrFromPath(
| "kafka_trigger"
| "nats_trigger"
| "postgres_trigger"
| "sqs_trigger"
| "user"
| "group"
| "settings"
@@ -242,6 +245,7 @@ export function getTypeStrFromPath(
typeEnding === "kafka_trigger" ||
typeEnding === "nats_trigger" ||
typeEnding === "postgres_trigger" ||
typeEnding === "sqs_trigger" ||
typeEnding === "user" ||
typeEnding === "group" ||
typeEnding === "settings" ||
+8 -1
View File
@@ -17,7 +17,8 @@
WebsocketTriggerService,
KafkaTriggerService,
PostgresTriggerService,
NatsTriggerService
NatsTriggerService,
SqsTriggerService
} from '$lib/gen'
import { superadmin, userStore, workspaceStore } from '$lib/stores'
import { createEventDispatcher, getContext } from 'svelte'
@@ -43,6 +44,7 @@
| 'kafka_trigger'
| 'postgres_trigger'
| 'nats_trigger'
| 'sqs_trigger'
let meta: Meta | undefined = undefined
export let fullNamePlaceholder: string | undefined = undefined
export let namePlaceholder = ''
@@ -246,6 +248,11 @@
workspace: $workspaceStore!,
path: path
})
} else if (kind == 'sqs_trigger') {
return await SqsTriggerService.existsSqsTrigger({
workspace: $workspaceStore!,
path: path
})
} else {
return false
}
@@ -28,6 +28,7 @@
| 'websocket_trigger'
| 'kafka_trigger'
| 'nats_trigger'
| 'sqs_trigger'
| 'postgres_trigger'
let kind: Kind
@@ -15,6 +15,7 @@
| 'postgres'
| 'scheduledPoll'
| 'kafka'
| 'sqs'
| 'nats' = 'webhooks'
export let flow_json: any | undefined = undefined
export let simplfiedPoll: boolean = false
@@ -54,9 +55,10 @@
<slot slot="webhooks" name="webhooks" />
<slot slot="routes" name="routes" />
<slot slot="websockets" name="websockets" />
<slot slot="kafka" name="kafka" />
<slot slot="postgres" name="postgres" />
<slot slot="kafka" name="kafka" />
<slot slot="nats" name="nats" />
<slot slot="sqs" name="sqs" />
<slot slot="emails" name="emails" />
<slot slot="schedules" name="schedules" />
<slot slot="cli" name="cli" />
@@ -30,6 +30,7 @@
| 'scheduledPoll'
| 'kafka'
| 'nats'
| 'sqs'
>('webhooks')
const simplifiedPoll = writable(false)
@@ -64,9 +65,10 @@
<slot slot="webhooks" name="webhooks" />
<slot slot="routes" name="routes" />
<slot slot="websockets" name="websockets" />
<slot slot="kafka" name="kafka" />
<slot slot="postgres" name="postgres" />
<slot slot="kafka" name="kafka" />
<slot slot="nats" name="nats" />
<slot slot="sqs" name="sqs" />
<slot slot="emails" name="emails" />
<slot slot="schedules" name="schedules" />
<slot slot="cli" name="cli" />
@@ -111,9 +113,10 @@
<slot slot="routes" name="routes" />
<slot slot="script" name="script" />
<slot slot="websockets" name="websockets" />
<slot slot="kafka" name="kafka" />
<slot slot="postgres" name="postgres" />
<slot slot="kafka" name="kafka" />
<slot slot="nats" name="nats" />
<slot slot="sqs" name="sqs" />
<slot slot="emails" name="emails" />
<slot slot="schedules" name="schedules" />
<slot slot="cli" name="cli" />
@@ -8,9 +8,7 @@
Webhook,
Unplug,
PlugZap,
Database
} from 'lucide-svelte'
import HighlightTheme from '../HighlightTheme.svelte'
@@ -18,6 +16,7 @@
import NatsIcon from '../icons/NatsIcon.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import { AwsIcon } from '../icons'
export let triggerSelected:
| 'webhooks'
@@ -29,13 +28,14 @@
| 'kafka'
| 'postgres'
| 'nats'
| 'sqs'
| 'scheduledPoll' = 'webhooks'
export let simplfiedPoll: boolean = false
export let eventStreamType: 'kafka' | 'nats' = 'kafka'
export let eventStreamType: 'kafka' | 'nats' | 'sqs' = 'kafka'
$: {
if (triggerSelected === 'kafka' || triggerSelected === 'nats') {
if (triggerSelected === 'kafka' || triggerSelected === 'nats' || triggerSelected === 'sqs') {
eventStreamType = triggerSelected
}
}
@@ -76,7 +76,7 @@
Postgres
</span>
</Tab>
<Tab value="kafka" otherValues={['nats']}>
<Tab value="kafka" otherValues={['nats', 'sqs']}>
<span class="flex flex-row gap-2 items-center text-xs">
<PlugZap size={12} />
Event streams
@@ -109,17 +109,20 @@
<slot name="websockets" />
{:else if triggerSelected === 'postgres'}
<slot name="postgres" />
{:else if triggerSelected === 'kafka' || triggerSelected === 'nats'}
{:else if triggerSelected === 'kafka' || triggerSelected === 'nats' || triggerSelected === 'sqs'}
<div class="m-1.5">
<ToggleButtonGroup bind:selected={eventStreamType}>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} />
</ToggleButtonGroup>
</div>
{#if eventStreamType === 'kafka'}
<slot name="kafka" />
{:else if eventStreamType === 'nats'}
<slot name="nats" />
{:else if eventStreamType === 'sqs'}
<slot name="sqs" />
{/if}
{:else if triggerSelected === 'cli'}
<slot name="cli" />
@@ -12,6 +12,7 @@
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte'
import NatsIcon from '$lib/components/icons/NatsIcon.svelte'
import AwsIcon from '$lib/components/icons/AwsIcon.svelte'
const { selectedTrigger, triggersCount } = getContext<TriggerContext>('TriggerContext')
@@ -30,6 +31,7 @@
| 'emails'
| 'eventStreams'
| 'postgres'
| 'sqs'
)[] = showOnlyWithCount
? ['webhooks', 'schedules', 'routes', 'websockets', 'kafka', 'nats', 'emails']
: ['webhooks', 'schedules', 'routes', 'websockets', 'eventStreams', 'emails']
@@ -66,6 +68,7 @@
kafka: { icon: KafkaIcon, countKey: 'kafka_count' },
emails: { icon: Mail, countKey: 'email_count' },
nats: { icon: NatsIcon, countKey: 'nats_count' },
sqs: { icon: AwsIcon, countKey: 'sqs_count' },
eventStreams: { icon: PlugZap }
}
@@ -77,7 +80,7 @@
{#each triggersToDisplay as type}
{@const { icon, countKey } = triggerTypeConfig[type]}
{#if (!showOnlyWithCount || ((countKey && $triggersCount?.[countKey]) || 0) > 0) && !(type === 'kafka' && !$enterpriseLicense) && !(type === 'nats' && !$enterpriseLicense)}
{#if (!showOnlyWithCount || ((countKey && $triggersCount?.[countKey]) || 0) > 0) && !(type === 'sqs' && !$enterpriseLicense) && !(type === 'kafka' && !$enterpriseLicense) && !(type === 'nats' && !$enterpriseLicense)}
<Popover>
<svelte:fragment slot="text">{camelCaseToWords(type)}</svelte:fragment>
<TriggerButton
@@ -88,7 +91,9 @@
selected={selected &&
($selectedTrigger === type ||
(type === 'eventStreams' &&
($selectedTrigger === 'kafka' || $selectedTrigger === 'nats')))}
($selectedTrigger === 'kafka' ||
$selectedTrigger === 'nats' ||
$selectedTrigger === 'sqs')))}
>
{#if countKey}
<TriggerCount count={$triggersCount?.[countKey]} />
@@ -1,10 +1,29 @@
<script lang="ts">
export let height = '24px'
export let width = '24px'
export let size: number
export let color: string | undefined = undefined
let clazz = ''
export { clazz as class }
</script>
<svg {width} {height} viewBox="0 0 168 168" fill="none" xmlns="http://www.w3.org/2000/svg" style="fill:currentcolor;">
<path d="M47.7805 70.052C47.7805 72.0948 48.0013 73.7511 48.3878 74.9657C48.8295 76.1803 49.3816 77.5054 50.1545 78.9408C50.4306 79.3825 50.541 79.8242 50.541 80.2106C50.541 80.7627 50.2097 81.3148 49.492 81.8669L46.0138 84.1858C45.5169 84.517 45.02 84.6826 44.5783 84.6826C44.0262 84.6826 43.4741 84.4066 42.922 83.9097C42.1491 83.0816 41.4866 82.1982 40.9345 81.3148C40.3824 80.3763 39.8303 79.3273 39.223 78.0575C34.9166 83.1368 29.506 85.6764 22.9913 85.6764C18.3536 85.6764 14.6546 84.3514 11.9493 81.7013C9.24402 79.0512 7.86377 75.5178 7.86377 71.101C7.86377 66.4082 9.52007 62.5987 12.8879 59.7278C16.2557 56.8569 20.7277 55.4214 26.4143 55.4214C28.2914 55.4214 30.2238 55.5871 32.2665 55.8631C34.3093 56.1392 36.4073 56.5808 38.6157 57.0777V53.0474C38.6157 48.8515 37.7323 45.9253 36.0208 44.2138C34.2541 42.5023 31.2727 41.6742 27.0216 41.6742C25.0892 41.6742 23.1017 41.895 21.0589 42.3919C19.0162 42.8888 17.0286 43.4961 15.0963 44.269C14.2129 44.6555 13.5504 44.8763 13.1639 44.9868C12.7774 45.0972 12.5014 45.1524 12.2806 45.1524C11.5076 45.1524 11.1212 44.6003 11.1212 43.4409V40.7356C11.1212 39.8522 11.2316 39.1897 11.5076 38.8033C11.7837 38.4168 12.2806 38.0303 13.0535 37.6439C14.9858 36.6501 17.3047 35.8219 20.0099 35.1594C22.7152 34.4417 25.5861 34.1104 28.6227 34.1104C35.1926 34.1104 39.9959 35.6011 43.0877 38.5824C46.1242 41.5637 47.6701 46.091 47.6701 52.164V70.052H47.7805ZM25.3653 78.4439C27.1872 78.4439 29.0644 78.1127 31.0519 77.4502C33.0395 76.7876 34.8062 75.573 36.2968 73.9167C37.1802 72.8677 37.8427 71.7083 38.174 70.3833C38.5052 69.0583 38.7261 67.4572 38.7261 65.58V63.2612C37.125 62.8748 35.4135 62.5435 33.6468 62.3227C31.8801 62.1018 30.1685 61.9914 28.457 61.9914C24.758 61.9914 22.0527 62.7091 20.2308 64.1998C18.4088 65.6905 17.5255 67.7884 17.5255 70.5489C17.5255 73.1438 18.188 75.0761 19.5683 76.4012C20.8933 77.7814 22.8256 78.4439 25.3653 78.4439ZM69.6988 84.4066C68.705 84.4066 68.0425 84.241 67.6008 83.8545C67.1591 83.5232 66.7727 82.7503 66.4414 81.7013L53.4671 39.0241C53.1358 37.9199 52.9702 37.2022 52.9702 36.8157C52.9702 35.9323 53.4119 35.4355 54.2952 35.4355H59.7058C60.7548 35.4355 61.4725 35.6011 61.859 35.9876C62.3007 36.3188 62.6319 37.0918 62.9632 38.1407L72.2384 74.6897L80.8512 38.1407C81.1272 37.0365 81.4585 36.3188 81.9002 35.9876C82.3418 35.6563 83.1148 35.4355 84.1086 35.4355H88.5253C89.5743 35.4355 90.2921 35.6011 90.7337 35.9876C91.1754 36.3188 91.5619 37.0918 91.7827 38.1407L100.506 75.1313L110.057 38.1407C110.388 37.0365 110.775 36.3188 111.161 35.9876C111.603 35.6563 112.321 35.4355 113.315 35.4355H118.449C119.332 35.4355 119.829 35.8771 119.829 36.8157C119.829 37.0918 119.774 37.3678 119.719 37.6991C119.664 38.0303 119.553 38.472 119.332 39.0793L106.027 81.7565C105.696 82.8607 105.309 83.5784 104.867 83.9097C104.426 84.241 103.708 84.4618 102.769 84.4618H98.0214C96.9725 84.4618 96.2547 84.2962 95.813 83.9097C95.3714 83.5232 94.9849 82.8055 94.7641 81.7013L86.2065 46.091L77.7042 81.6461C77.4282 82.7503 77.0969 83.468 76.6552 83.8545C76.2136 84.241 75.4406 84.4066 74.4468 84.4066H69.6988ZM140.643 85.8973C137.773 85.8973 134.902 85.566 132.141 84.9035C129.381 84.241 127.227 83.5232 125.792 82.6951C124.909 82.1982 124.301 81.6461 124.08 81.1492C123.86 80.6523 123.749 80.1002 123.749 79.6033V76.7876C123.749 75.6282 124.191 75.0761 125.019 75.0761C125.35 75.0761 125.682 75.1313 126.013 75.2418C126.344 75.3522 126.841 75.573 127.393 75.7939C129.27 76.622 131.313 77.2845 133.466 77.7262C135.675 78.1679 137.828 78.3887 140.036 78.3887C143.514 78.3887 146.22 77.7814 148.097 76.5668C149.974 75.3522 150.968 73.5855 150.968 71.3219C150.968 69.776 150.471 68.5062 149.477 67.4572C148.483 66.4082 146.606 65.4696 143.901 64.5863L135.895 62.1018C131.865 60.832 128.884 58.9549 127.062 56.4704C125.24 54.0412 124.301 51.3359 124.301 48.465C124.301 46.1462 124.798 44.1034 125.792 42.3367C126.786 40.57 128.111 39.0241 129.767 37.8095C131.423 36.5397 133.301 35.6011 135.509 34.9386C137.717 34.276 140.036 34 142.465 34C143.68 34 144.95 34.0552 146.164 34.2208C147.434 34.3865 148.594 34.6073 149.753 34.8281C150.857 35.1042 151.906 35.3802 152.9 35.7115C153.894 36.0428 154.667 36.374 155.219 36.7053C155.992 37.147 156.544 37.5886 156.875 38.0855C157.206 38.5272 157.372 39.1345 157.372 39.9075V42.5023C157.372 43.6617 156.93 44.269 156.102 44.269C155.661 44.269 154.943 44.0482 154.004 43.6065C150.857 42.1711 147.324 41.4533 143.404 41.4533C140.257 41.4533 137.773 41.9502 136.061 42.9992C134.35 44.0482 133.466 45.6493 133.466 47.9129C133.466 49.4588 134.018 50.7838 135.122 51.8328C136.227 52.8818 138.269 53.9308 141.196 54.8693L149.035 57.3538C153.01 58.6236 155.881 60.3903 157.593 62.6539C159.304 64.9175 160.133 67.5124 160.133 70.3833C160.133 72.7573 159.636 74.9105 158.697 76.7876C157.703 78.6648 156.378 80.3211 154.667 81.6461C152.955 83.0263 150.912 84.0201 148.538 84.7379C146.054 85.5108 143.459 85.8973 140.643 85.8973Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M151.078 112.729C132.914 126.145 106.524 133.267 83.8324 133.267C52.0316 133.267 23.3777 121.508 1.7354 101.963C0.0238993 100.417 1.56977 98.3195 3.61254 99.5341C27.0215 113.116 55.8963 121.342 85.7648 121.342C105.916 121.342 128.056 117.146 148.428 108.533C151.465 107.153 154.059 110.521 151.078 112.729Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M158.642 104.117C156.323 101.135 143.294 102.681 137.386 103.399C135.62 103.62 135.343 102.074 136.945 100.914C147.324 93.6267 164.384 95.7246 166.371 98.1539C168.359 100.638 165.819 117.698 156.102 125.869C154.612 127.139 153.176 126.477 153.839 124.82C156.047 119.354 160.961 107.043 158.642 104.117Z"/>
<svg
width={size}
height={size}
viewBox="0 0 168 168"
class={clazz}
xmlns="http://www.w3.org/2000/svg"
fill={color ?? 'currentColor'}
>
<path
d="M47.7805 70.052C47.7805 72.0948 48.0013 73.7511 48.3878 74.9657C48.8295 76.1803 49.3816 77.5054 50.1545 78.9408C50.4306 79.3825 50.541 79.8242 50.541 80.2106C50.541 80.7627 50.2097 81.3148 49.492 81.8669L46.0138 84.1858C45.5169 84.517 45.02 84.6826 44.5783 84.6826C44.0262 84.6826 43.4741 84.4066 42.922 83.9097C42.1491 83.0816 41.4866 82.1982 40.9345 81.3148C40.3824 80.3763 39.8303 79.3273 39.223 78.0575C34.9166 83.1368 29.506 85.6764 22.9913 85.6764C18.3536 85.6764 14.6546 84.3514 11.9493 81.7013C9.24402 79.0512 7.86377 75.5178 7.86377 71.101C7.86377 66.4082 9.52007 62.5987 12.8879 59.7278C16.2557 56.8569 20.7277 55.4214 26.4143 55.4214C28.2914 55.4214 30.2238 55.5871 32.2665 55.8631C34.3093 56.1392 36.4073 56.5808 38.6157 57.0777V53.0474C38.6157 48.8515 37.7323 45.9253 36.0208 44.2138C34.2541 42.5023 31.2727 41.6742 27.0216 41.6742C25.0892 41.6742 23.1017 41.895 21.0589 42.3919C19.0162 42.8888 17.0286 43.4961 15.0963 44.269C14.2129 44.6555 13.5504 44.8763 13.1639 44.9868C12.7774 45.0972 12.5014 45.1524 12.2806 45.1524C11.5076 45.1524 11.1212 44.6003 11.1212 43.4409V40.7356C11.1212 39.8522 11.2316 39.1897 11.5076 38.8033C11.7837 38.4168 12.2806 38.0303 13.0535 37.6439C14.9858 36.6501 17.3047 35.8219 20.0099 35.1594C22.7152 34.4417 25.5861 34.1104 28.6227 34.1104C35.1926 34.1104 39.9959 35.6011 43.0877 38.5824C46.1242 41.5637 47.6701 46.091 47.6701 52.164V70.052H47.7805ZM25.3653 78.4439C27.1872 78.4439 29.0644 78.1127 31.0519 77.4502C33.0395 76.7876 34.8062 75.573 36.2968 73.9167C37.1802 72.8677 37.8427 71.7083 38.174 70.3833C38.5052 69.0583 38.7261 67.4572 38.7261 65.58V63.2612C37.125 62.8748 35.4135 62.5435 33.6468 62.3227C31.8801 62.1018 30.1685 61.9914 28.457 61.9914C24.758 61.9914 22.0527 62.7091 20.2308 64.1998C18.4088 65.6905 17.5255 67.7884 17.5255 70.5489C17.5255 73.1438 18.188 75.0761 19.5683 76.4012C20.8933 77.7814 22.8256 78.4439 25.3653 78.4439ZM69.6988 84.4066C68.705 84.4066 68.0425 84.241 67.6008 83.8545C67.1591 83.5232 66.7727 82.7503 66.4414 81.7013L53.4671 39.0241C53.1358 37.9199 52.9702 37.2022 52.9702 36.8157C52.9702 35.9323 53.4119 35.4355 54.2952 35.4355H59.7058C60.7548 35.4355 61.4725 35.6011 61.859 35.9876C62.3007 36.3188 62.6319 37.0918 62.9632 38.1407L72.2384 74.6897L80.8512 38.1407C81.1272 37.0365 81.4585 36.3188 81.9002 35.9876C82.3418 35.6563 83.1148 35.4355 84.1086 35.4355H88.5253C89.5743 35.4355 90.2921 35.6011 90.7337 35.9876C91.1754 36.3188 91.5619 37.0918 91.7827 38.1407L100.506 75.1313L110.057 38.1407C110.388 37.0365 110.775 36.3188 111.161 35.9876C111.603 35.6563 112.321 35.4355 113.315 35.4355H118.449C119.332 35.4355 119.829 35.8771 119.829 36.8157C119.829 37.0918 119.774 37.3678 119.719 37.6991C119.664 38.0303 119.553 38.472 119.332 39.0793L106.027 81.7565C105.696 82.8607 105.309 83.5784 104.867 83.9097C104.426 84.241 103.708 84.4618 102.769 84.4618H98.0214C96.9725 84.4618 96.2547 84.2962 95.813 83.9097C95.3714 83.5232 94.9849 82.8055 94.7641 81.7013L86.2065 46.091L77.7042 81.6461C77.4282 82.7503 77.0969 83.468 76.6552 83.8545C76.2136 84.241 75.4406 84.4066 74.4468 84.4066H69.6988ZM140.643 85.8973C137.773 85.8973 134.902 85.566 132.141 84.9035C129.381 84.241 127.227 83.5232 125.792 82.6951C124.909 82.1982 124.301 81.6461 124.08 81.1492C123.86 80.6523 123.749 80.1002 123.749 79.6033V76.7876C123.749 75.6282 124.191 75.0761 125.019 75.0761C125.35 75.0761 125.682 75.1313 126.013 75.2418C126.344 75.3522 126.841 75.573 127.393 75.7939C129.27 76.622 131.313 77.2845 133.466 77.7262C135.675 78.1679 137.828 78.3887 140.036 78.3887C143.514 78.3887 146.22 77.7814 148.097 76.5668C149.974 75.3522 150.968 73.5855 150.968 71.3219C150.968 69.776 150.471 68.5062 149.477 67.4572C148.483 66.4082 146.606 65.4696 143.901 64.5863L135.895 62.1018C131.865 60.832 128.884 58.9549 127.062 56.4704C125.24 54.0412 124.301 51.3359 124.301 48.465C124.301 46.1462 124.798 44.1034 125.792 42.3367C126.786 40.57 128.111 39.0241 129.767 37.8095C131.423 36.5397 133.301 35.6011 135.509 34.9386C137.717 34.276 140.036 34 142.465 34C143.68 34 144.95 34.0552 146.164 34.2208C147.434 34.3865 148.594 34.6073 149.753 34.8281C150.857 35.1042 151.906 35.3802 152.9 35.7115C153.894 36.0428 154.667 36.374 155.219 36.7053C155.992 37.147 156.544 37.5886 156.875 38.0855C157.206 38.5272 157.372 39.1345 157.372 39.9075V42.5023C157.372 43.6617 156.93 44.269 156.102 44.269C155.661 44.269 154.943 44.0482 154.004 43.6065C150.857 42.1711 147.324 41.4533 143.404 41.4533C140.257 41.4533 137.773 41.9502 136.061 42.9992C134.35 44.0482 133.466 45.6493 133.466 47.9129C133.466 49.4588 134.018 50.7838 135.122 51.8328C136.227 52.8818 138.269 53.9308 141.196 54.8693L149.035 57.3538C153.01 58.6236 155.881 60.3903 157.593 62.6539C159.304 64.9175 160.133 67.5124 160.133 70.3833C160.133 72.7573 159.636 74.9105 158.697 76.7876C157.703 78.6648 156.378 80.3211 154.667 81.6461C152.955 83.0263 150.912 84.0201 148.538 84.7379C146.054 85.5108 143.459 85.8973 140.643 85.8973Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M151.078 112.729C132.914 126.145 106.524 133.267 83.8324 133.267C52.0316 133.267 23.3777 121.508 1.7354 101.963C0.0238993 100.417 1.56977 98.3195 3.61254 99.5341C27.0215 113.116 55.8963 121.342 85.7648 121.342C105.916 121.342 128.056 117.146 148.428 108.533C151.465 107.153 154.059 110.521 151.078 112.729Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M158.642 104.117C156.323 101.135 143.294 102.681 137.386 103.399C135.62 103.62 135.343 102.074 136.945 100.914C147.324 93.6267 164.384 95.7246 166.371 98.1539C168.359 100.638 165.819 117.698 156.102 125.869C154.612 127.139 153.176 126.477 153.839 124.82C156.047 119.354 160.961 107.043 158.642 104.117Z"
/>
</svg>
@@ -83,6 +83,11 @@
id: 'triggers',
href: `${base}/nats_triggers`
},
{
label: 'SQS triggers',
id: 'triggers',
href: `${base}/sqs_triggers`
},
{
label: 'Audit logs',
id: 'audit_logs',
@@ -53,6 +53,7 @@
import SideBarNotification from './SideBarNotification.svelte'
import KafkaIcon from '../icons/KafkaIcon.svelte'
import NatsIcon from '../icons/NatsIcon.svelte'
import AwsIcon from '../icons/AwsIcon.svelte'
export let numUnacknowledgedCriticalAlerts = 0
@@ -121,6 +122,13 @@
icon: NatsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'nats'
},
{
label: 'SQS' + ($enterpriseLicense ? '' : ' (EE)'),
href: '/sqs_triggers',
icon: AwsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'sqs'
}
]
+3
View File
@@ -51,6 +51,7 @@ export type TriggerKind =
| 'kafka'
| 'nats'
| 'postgres'
| 'sqs'
export function captureTriggerKindToTriggerKind(kind: CaptureTriggerKind): TriggerKind {
switch (kind) {
case 'webhook':
@@ -65,6 +66,8 @@ export function captureTriggerKindToTriggerKind(kind: CaptureTriggerKind): Trigg
return 'kafka'
case 'nats':
return 'nats'
case 'sqs':
return 'sqs'
case 'postgres':
return 'postgres'
default:
@@ -9,6 +9,7 @@
import { captureTriggerKindToTriggerKind } from '../triggers'
import CaptureIcon from './CaptureIcon.svelte'
import NatsIcon from '../icons/NatsIcon.svelte'
import AwsIcon from '../icons/AwsIcon.svelte'
export let small = false
@@ -83,6 +84,15 @@
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('sqs')}
>
<div class="flex flex-row items-center gap-2">
<AwsIcon size={16} />
<p class="text-xs text-secondary">SQS</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('postgres')}
>
@@ -14,6 +14,7 @@
import type { CaptureInfo } from './CaptureSection.svelte'
import CaptureTable from './CaptureTable.svelte'
import NatsTriggersConfigSection from './nats/NatsTriggersConfigSection.svelte'
import SqsTriggerEditorConfigSection from './sqs/SqsTriggerEditorConfigSection.svelte'
import PostgresEditorConfigSection from './postgres/PostgresEditorConfigSection.svelte'
import { invalidRelations } from './postgres/utils'
@@ -79,7 +80,7 @@
}, {})
if (
(captureType === 'postgres' || captureType === 'websocket' || captureType === 'kafka') &&
(captureType === 'postgres' || captureType === 'websocket' || captureType === 'kafka' || captureType === 'sqs') &&
captureActive
) {
const config = captureConfigs[captureType]
@@ -150,12 +151,11 @@
let config: CaptureConfig | undefined
$: config = captureConfigs[captureType]
const streamingTrigger = ['postgres', 'websocket', 'kafka', 'nats']
let cloudDisabled = streamingTrigger.includes(captureType) && isCloudHosted()
const streamingCaptures = ['sqs', 'websocket', 'postgres', 'kafka', 'nats']
let cloudDisabled = streamingCaptures.includes(captureType) && isCloudHosted()
function updateConnectionInfo(config: CaptureConfig | undefined, captureActive: boolean) {
if (streamingTrigger.includes(captureType) && config && captureActive) {
if (streamingCaptures.includes(captureType) && config && captureActive) {
const serverEnabled = getServerEnabled(config)
const connected = serverEnabled && !config.error
const message = connected
@@ -299,6 +299,22 @@
on:addPreprocessor
on:captureToggle={handleCapture}
/>
{:else if captureType === 'sqs'}
<SqsTriggerEditorConfigSection
can_write={true}
headless={true}
bind:queue_url={args.queue_url}
bind:aws_resource_path={args.aws_resource_path}
bind:message_attributes={args.message_attributes}
{showCapture}
{captureInfo}
bind:captureTable
on:applyArgs
on:updateSchema
on:addPreprocessor
on:captureToggle={handleCapture}
on:testWithArgs
/>
{/if}
</div>
{/key}
@@ -3,6 +3,7 @@
CancelablePromise,
KafkaTriggerService,
NatsTriggerService,
SqsTriggerService,
PostgresTriggerService,
WebsocketTriggerService
} from '$lib/gen'
@@ -10,7 +11,7 @@
import { sendUserToast } from '$lib/toast'
import Button from '../common/button/Button.svelte'
export let kind: 'websocket' | 'nats' | 'kafka' | 'postgres'
export let kind: 'websocket' | 'nats' | 'kafka' | 'postgres' | 'sqs'
export let args: Record<string, any>
export let noButton = false
export let testLoading: boolean = false
@@ -19,6 +20,7 @@
websocket: 'WebSocket',
nats: 'NATS server(s)',
kafka: 'Kafka broker(s)',
sqs: 'SQS',
postgres: 'Postgres'
}
@@ -46,6 +48,11 @@
workspace: $workspaceStore!,
requestBody: args as any
})
} else if (kind === 'sqs') {
promise = SqsTriggerService.testSqsConnection({
workspace: $workspaceStore!,
requestBody: args as any
})
} else if (kind === 'postgres') {
promise = PostgresTriggerService.testPostgresConnection({
workspace: $workspaceStore!,
@@ -15,9 +15,10 @@
import PostgresTriggersPanel from './postgres/PostgresTriggersPanel.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import { KafkaIcon, NatsIcon } from '../icons'
import { AwsIcon, KafkaIcon, NatsIcon } from '../icons'
import KafkaTriggersPanel from './kafka/KafkaTriggersPanel.svelte'
import NatsTriggersPanel from './nats/NatsTriggersPanel.svelte'
import SqsTriggerPanel from './sqs/SqsTriggerPanel.svelte'
export let noEditor: boolean
export let newItem = false
@@ -29,10 +30,10 @@
export let canHavePreprocessor: boolean = false
export let hasPreprocessor: boolean = false
export let args: Record<string, any> = {}
let eventStreamType: 'kafka' | 'nats' = 'kafka'
let eventStreamType: 'kafka' | 'nats' | 'sqs' = 'kafka'
$: {
if ($selectedTrigger === 'kafka' || $selectedTrigger === 'nats') {
if ($selectedTrigger === 'kafka' || $selectedTrigger === 'nats' || $selectedTrigger === 'sqs') {
eventStreamType = $selectedTrigger
}
}
@@ -40,7 +41,6 @@
const { selectedTrigger, simplifiedPoll } = getContext<TriggerContext>('TriggerContext')
const dispatch = createEventDispatcher()
onDestroy(() => {
dispatch('exitTriggers')
})
@@ -57,7 +57,7 @@
<Tab value="postgres" selectedClass="text-primary text-sm font-semibold">Postgres</Tab>
<Tab
value="kafka"
otherValues={['nats']}
otherValues={['sqs', 'nats']}
selectedClass="text-primary text-sm font-semibold"
>
Event streams
@@ -153,11 +153,12 @@
isEditor={true}
/>
</div>
{:else if $selectedTrigger === 'kafka' || $selectedTrigger === 'nats'}
{:else if $selectedTrigger === 'kafka' || $selectedTrigger === 'nats' || $selectedTrigger === 'sqs'}
<div class="p-4 flex flex-col gap-2">
<ToggleButtonGroup bind:selected={eventStreamType}>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} />
</ToggleButtonGroup>
{#if eventStreamType === 'kafka'}
<KafkaTriggersPanel
@@ -183,6 +184,19 @@
{canHavePreprocessor}
{hasPreprocessor}
/>
{:else if eventStreamType === 'sqs'}
<SqsTriggerPanel
on:applyArgs
on:addPreprocessor
on:updateSchema
on:testWithArgs
{newItem}
path={currentPath}
{isFlow}
isEditor={true}
{canHavePreprocessor}
{hasPreprocessor}
/>
{/if}
</div>
{:else if $selectedTrigger === 'schedules'}
@@ -32,6 +32,7 @@
kafka: '+ New Kafka trigger',
email: 'Email trigger',
nats: '+ New NATS trigger',
sqs: '+ New SQS trigger',
postgres: '+ New Postgres trigger'
}
@@ -8,6 +8,7 @@
import EmailTriggerConfigSection from '../details/EmailTriggerConfigSection.svelte'
import KafkaTriggersConfigSection from './kafka/KafkaTriggersConfigSection.svelte'
import NatsTriggersConfigSection from './nats/NatsTriggersConfigSection.svelte'
import SqsTriggerEditorConfigSection from './sqs/SqsTriggerEditorConfigSection.svelte'
import PostgresEditorConfigSection from './postgres/PostgresEditorConfigSection.svelte'
export let triggerType: CaptureTriggerKind = 'webhook'
@@ -70,5 +71,14 @@
<KafkaTriggersConfigSection headless={true} bind:args staticInputDisabled={false} {path} />
{:else if triggerType === 'nats'}
<NatsTriggersConfigSection headless={true} bind:args staticInputDisabled={false} {path} />
{:else if triggerType === 'sqs'}
<SqsTriggerEditorConfigSection
bind:queue_url={args.queue_url}
bind:aws_resource_path={args.aws_resource_path}
bind:message_attributes={args.message_attributes}
headless={true}
can_write={true}
showCapture={false}
/>
{/if}
</div>
@@ -120,26 +120,28 @@
{/if}
<div class="flex flex-col w-full gap-4">
<label class="block grow w-full">
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
<div>
Path
<Required required={true} />
<div class="flex flex-col gap-1">
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
<div>
Path
<Required required={true} />
</div>
<div class="text-2xs text-tertiary"> ':myparam' for path params </div>
</div>
<div class="text-2xs text-tertiary"> ':myparam' for path params </div>
<!-- svelte-ignore a11y-autofocus -->
<input
type="text"
autocomplete="off"
bind:value={route_path}
disabled={!($userStore?.is_admin || $userStore?.is_super_admin) || !can_write}
class={routeError === ''
? ''
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
on:input={() => {
dirtyRoutePath = true
}}
/>
</div>
<!-- svelte-ignore a11y-autofocus -->
<input
type="text"
autocomplete="off"
bind:value={route_path}
disabled={!($userStore?.is_admin || $userStore?.is_super_admin) || !can_write}
class={routeError === ''
? ''
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
on:input={() => {
dirtyRoutePath = true
}}
/>
</label>
<ToggleButtonGroup
@@ -226,16 +226,6 @@
</Label>
</div>
<RouteEditorConfigSection
initialTriggerPath={initialPath}
bind:route_path
bind:isValid
bind:dirtyRoutePath
bind:http_method
{can_write}
bind:static_asset_config
/>
<Section label="Target">
{#if !isCloudHosted()}
<ToggleButtonGroup
@@ -376,6 +366,16 @@
{/if}
</Section>
<RouteEditorConfigSection
initialTriggerPath={initialPath}
bind:route_path
bind:isValid
bind:dirtyRoutePath
bind:http_method
{can_write}
bind:static_asset_config
/>
{#if !is_static_website}
<Section label="Advanced">
<div class="flex flex-col gap-4">
@@ -215,13 +215,6 @@
</Label>
</div>
<KafkaTriggersConfigSection
bind:args
bind:isValid
{path}
defaultValues={useDefaultValues() ? defaultValues : undefined}
/>
<Section label="Runnable">
<p class="text-xs mb-1 text-tertiary">
Pick a script or flow to be triggered<Required required={true} />
@@ -239,6 +232,13 @@
/>
</div>
</Section>
<KafkaTriggersConfigSection
bind:args
bind:isValid
{path}
defaultValues={useDefaultValues() ? defaultValues : undefined}
/>
</div>
{/if}
</DrawerContent>
@@ -222,14 +222,6 @@
/>
</Label>
</div>
<NatsTriggersConfigSection
{path}
bind:args
bind:isValid
defaultValues={useDefaultValues() ? defaultValues : undefined}
/>
<Section label="Runnable">
<p class="text-xs mb-1 text-tertiary">
Pick a script or flow to be triggered<Required required={true} />
@@ -247,6 +239,13 @@
/>
</div>
</Section>
<NatsTriggersConfigSection
{path}
bind:args
bind:isValid
defaultValues={useDefaultValues() ? defaultValues : undefined}
/>
</div>
{/if}
</DrawerContent>
@@ -141,7 +141,7 @@
</Label>
<Label label="Table tracking">
<svelte:fragment slot="header">
<Tooltip small>
<Tooltip documentationLink="https://www.windmill.dev/docs/core_concepts/postgres_triggers#define-what-to-track" small>
Select the tables to track. You can choose to track
<strong>all tables in your database</strong>,
<strong>all tables within a specific schema</strong>,
@@ -425,7 +425,9 @@
</Label>
<Label label="Table Tracking">
<svelte:fragment slot="header">
<Tooltip>
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/postgres_triggers#define-what-to-track"
>
<p>
Select the tables to track. You can choose to track
<strong>all tables in your database</strong>,
@@ -69,7 +69,7 @@
</Tooltip>
</svelte:fragment>
<input type="text" bind:value={v.schema_name} />
<input class="mt-1" type="text" bind:value={v.schema_name} />
</Label>
<div class="flex flex-col w-full gap-4 items-center p-5">
{#each v.table_to_track as table_to_track, j}
@@ -83,7 +83,7 @@
<input
type="text"
bind:value={table_to_track.table_name}
class="!bg-surface"
class="!bg-surface mt-1"
/>
</Label>
<!-- svelte-ignore a11y-label-has-associated-control -->
@@ -109,25 +109,27 @@
</p>
</Tooltip>
</svelte:fragment>
<MultiSelect
options={table_to_track.columns_name ?? []}
allowUserOptions="append"
bind:selected={table_to_track.columns_name}
ulOptionsClass={'!bg-surface !text-sm'}
ulSelectedClass="!text-sm"
outerDivClass="!bg-surface !min-h-[38px] !border-[#d1d5db]"
noMatchingOptionsMsg=""
createOptionMsg={null}
duplicates={false}
placeholder="Select columns"
--sms-options-margin="4px"
>
<svelte:fragment slot="remove-icon">
<div class="hover:text-primary p-0.5">
<X size={12} />
</div>
</svelte:fragment>
</MultiSelect>
<div class="mt-1">
<MultiSelect
options={table_to_track.columns_name ?? []}
allowUserOptions="append"
bind:selected={table_to_track.columns_name}
ulOptionsClass={'!bg-surface !text-sm'}
ulSelectedClass="!text-sm"
outerDivClass="!bg-surface !min-h-[38px] !border-[#d1d5db]"
noMatchingOptionsMsg=""
createOptionMsg={null}
duplicates={false}
placeholder="Select columns"
--sms-options-margin="4px"
>
<svelte:fragment slot="remove-icon">
<div class="hover:text-primary p-0.5">
<X size={12} />
</div>
</svelte:fragment>
</MultiSelect>
</div>
</Label>
<Label label="Where Clause">
<svelte:fragment slot="header">
@@ -156,7 +158,7 @@
<input
type="text"
bind:value={table_to_track.where_clause}
class="!bg-surface"
class="!bg-surface mt-1"
/>
</Label>
<Button
@@ -0,0 +1,27 @@
<script lang="ts">
import { tick } from 'svelte'
import SqsTriggerEditorInner from './SqsTriggerEditorInner.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,
defaultValues?: Record<string, any>
) {
open = true
await tick()
drawer?.openNew(is_flow, initial_script_path, defaultValues)
}
let drawer: SqsTriggerEditorInner
</script>
{#if open}
<SqsTriggerEditorInner on:update bind:this={drawer} />
{/if}
@@ -0,0 +1,152 @@
<script lang="ts">
import Section from '$lib/components/Section.svelte'
import CaptureSection, { type CaptureInfo } from '../CaptureSection.svelte'
import CaptureTable from '../CaptureTable.svelte'
import Required from '$lib/components/Required.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import { emptyStringTrimmed } from '$lib/utils'
import MultiSelect from 'svelte-multiselect'
import TestTriggerConnection from '../TestTriggerConnection.svelte'
import Subsection from '$lib/components/Subsection.svelte'
import { X } from 'lucide-svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
export let can_write: boolean = false
export let headless: boolean = false
export let showCapture: boolean = false
export let captureTable: CaptureTable | undefined = undefined
export let captureInfo: CaptureInfo | undefined = undefined
export let isValid: boolean = false
export let queue_url = ''
export let aws_resource_path = ''
export let message_attributes: string[] = []
let cached: string[] = []
let dirtyUrl: boolean = false
let urlError: string = ''
let validateTimeout: NodeJS.Timeout | undefined = undefined
let all_attributes = message_attributes.includes('All')
let tab: 'specific' | 'all' = all_attributes ? 'all' : 'specific'
function validateUrl(queue_url: string | undefined) {
if (validateTimeout) {
clearTimeout(validateTimeout)
}
validateTimeout = setTimeout(() => {
if (!queue_url || /^(https:)\/\/[^\s]+$/.test(queue_url) === false) {
urlError = 'Queue url must start with https://'
} else {
urlError = ''
}
validateTimeout = undefined
}, 500)
}
$: validateUrl(queue_url)
$: isValid =
urlError === '' && !emptyStringTrimmed(aws_resource_path) && !emptyStringTrimmed(queue_url)
</script>
<div>
{#if showCapture && captureInfo}
<CaptureSection
captureType="sqs"
disabled={!isValid}
{captureInfo}
on:captureToggle
on:applyArgs
on:updateSchema
on:addPreprocessor
on:testWithArgs
bind:captureTable
/>
{/if}
<Section label="SQS" {headless}>
<div class="flex flex-col w-full gap-4">
<Subsection label="Connection setup">
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1">
<p class="text-xs mb-1 text-tertiary">
Select an AWS resource with credentials to authenticate your account. <Required
required={true}
/>
</p>
<ResourcePicker resourceType="aws" bind:value={aws_resource_path} />
{#if isValid}
<TestTriggerConnection kind="sqs" args={{ aws_resource_path, queue_url }} />
{/if}
</div>
<div class="flex flex-col gap-1">
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
<div class="flex flex-col gap-1">
<p class="text-xs text-tertiary">
Provide the URL of the SQS queue the application should listen to. <Required
required={true}
/>
</p>
</div>
</div>
<input
type="text"
autocomplete="off"
bind:value={queue_url}
disabled={!can_write}
on:input={() => {
dirtyUrl = true
}}
placeholder="https://example.com"
class={urlError === ''
? ''
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
/>
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">
{dirtyUrl ? urlError : ''}
</div>
</div>
</div>
</Subsection>
<Subsection
tooltip=" When 'All Attributes' is selected, all message attributes from the received message are included with the message. When 'Specific Attributes' is selected, only the specified attributes (up to a maximum of 10) are included if they are present in the message."
label="Message attributes"
>
<div class="mt-2">
<ToggleButtonGroup
bind:selected={tab}
on:selected={() => {
if (tab === 'all') {
cached = message_attributes
message_attributes = ['All']
} else {
message_attributes = cached
}
}}
>
<ToggleButton value="all" label="All attributes" />
<ToggleButton value="specific" label="Specific attributes" />
</ToggleButtonGroup>
</div>
<div class="flex flex-col mt-3 gap-1">
<MultiSelect
options={message_attributes ?? []}
allowUserOptions="append"
bind:selected={message_attributes}
ulOptionsClass={'!bg-surface !text-sm'}
ulSelectedClass="!text-sm"
outerDivClass="!bg-surface !min-h-[38px] !border-[#d1d5db]"
noMatchingOptionsMsg=""
createOptionMsg={null}
duplicates={false}
placeholder="Set message attributes"
--sms-options-margin="4px"
disabled={tab === 'all'}
>
<svelte:fragment slot="remove-icon">
<div class="hover:text-primary p-0.5">
<X size={12} />
</div>
</svelte:fragment>
</MultiSelect>
</div>
</Subsection>
</div>
</Section>
</div>
@@ -0,0 +1,241 @@
<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 { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { canWrite, emptyString, sendUserToast } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { Loader2, Save } from 'lucide-svelte'
import Label from '$lib/components/Label.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { SqsTriggerService } from '$lib/gen'
import SqsTriggerEditorConfigSection from './SqsTriggerEditorConfigSection.svelte'
import Section from '$lib/components/Section.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Required from '$lib/components/Required.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 enabled = false
let dirtyPath = false
let can_write = true
let drawerLoading = true
let aws_resource_path: string = ''
let queue_url = ''
let message_attributes: string[] = []
let isValid = false
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
await loadTrigger()
} catch (err) {
sendUserToast(`Could not load sqs trigger: ${err.body}`, true)
} finally {
drawerLoading = false
}
}
export async function openNew(
nis_flow: boolean,
fixedScriptPath_?: string,
defaultValues?: Record<string, any>
) {
drawerLoading = true
try {
drawer?.openDrawer()
is_flow = nis_flow
itemKind = nis_flow ? 'flow' : 'script'
initialScriptPath = ''
fixedScriptPath = fixedScriptPath_ ?? ''
script_path = fixedScriptPath
aws_resource_path = defaultValues?.aws_resource_path ?? ''
queue_url = defaultValues?.queue_url ?? ''
path = ''
message_attributes = defaultValues?.message_attributes ?? []
initialPath = ''
edit = false
dirtyPath = false
} finally {
drawerLoading = false
}
}
async function loadTrigger(): Promise<void> {
try {
const s = await SqsTriggerService.getSqsTrigger({
workspace: $workspaceStore!,
path: initialPath
})
script_path = s.script_path
initialScriptPath = s.script_path
aws_resource_path = s.aws_resource_path
queue_url = s.queue_url
is_flow = s.is_flow
message_attributes = s.message_attributes ?? []
path = s.path
enabled = s.enabled
can_write = canWrite(s.path, s.extra_perms, $userStore)
} catch (error) {
sendUserToast(`Could not load SQS trigger: ${error.body}`, true)
}
}
async function updateTrigger(): Promise<void> {
if (edit) {
await SqsTriggerService.updateSqsTrigger({
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
path,
script_path,
enabled,
is_flow,
queue_url,
aws_resource_path,
message_attributes
}
})
sendUserToast(`SQS trigger ${path} updated`)
} else {
await SqsTriggerService.createSqsTrigger({
workspace: $workspaceStore!,
requestBody: {
enabled: true,
aws_resource_path,
queue_url,
path,
script_path,
is_flow,
message_attributes
}
})
sendUserToast(`SQS trigger ${path} created`)
}
if (!$usedTriggerKinds.includes('sqs')) {
$usedTriggerKinds = [...$usedTriggerKinds, 'sqs']
}
dispatch('update')
drawer.closeDrawer()
}
</script>
<Drawer size="800px" bind:this={drawer}>
<DrawerContent
title={edit
? can_write
? `Edit SQS trigger ${initialPath}`
: `SQS trigger ${initialPath}`
: 'New SQS 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) => {
sendUserToast(`${e.detail ? 'enabled' : 'disabled'} sqs trigger ${initialPath}`)
}}
/>
</div>
{/if}
<Button
startIcon={{ icon: Save }}
disabled={pathError != '' || emptyString(script_path) || !isValid || !can_write}
on:click={updateTrigger}
>
Save
</Button>
{/if}
</svelte:fragment>
{#if drawerLoading}
<div class="flex flex-col items-center justify-center h-full w-full">
<Loader2 size="50" class="animate-spin" />
<p>Loading...</p>
</div>
{:else}
<div class="flex flex-col gap-5">
<Alert title="Info" type="info">
{#if edit}
Changes can take up to 30 seconds to take effect.
{:else}
New postgres triggers can take up to 30 seconds to start listening.
{/if}
</Alert>
</div>
<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="sqs_trigger"
kind="sqs_trigger"
disabled={!can_write}
/>
</Label>
</div>
<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
/>
{#if emptyString(script_path)}
<Button
btnClasses="ml-4 mt-2"
color="dark"
size="xs"
href={itemKind === 'flow' ? '/flows/add?hub=59' : '/scripts/add?hub=hub%2F11604'}
target="_blank">Create from template</Button
>
{/if}
</div>
</Section>
<SqsTriggerEditorConfigSection
bind:isValid
bind:queue_url
bind:message_attributes
bind:aws_resource_path
{can_write}
headless={true}
/>
</div>
{/if}
</DrawerContent>
</Drawer>
@@ -0,0 +1,135 @@
<script lang="ts">
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { SqsTriggerService, type SqsTrigger } from '$lib/gen'
import { canWrite } from '$lib/utils'
import { getContext, onMount } from 'svelte'
import { isCloudHosted } from '$lib/cloud'
import Section from '$lib/components/Section.svelte'
import { Alert } from '$lib/components/common'
import Description from '$lib/components/Description.svelte'
import type { TriggerContext } from '$lib/components/triggers'
import TriggersEditorSection from '../TriggersEditorSection.svelte'
import SqsTriggerEditor from './SqsTriggerEditor.svelte'
export let isFlow: boolean
export let path: string
export let newItem: boolean = false
export let isEditor: boolean = false
export let canHavePreprocessor: boolean = false
export let hasPreprocessor: boolean = false
let sqsTriggerEditor: SqsTriggerEditor
let openForm = true
let dontCloseOnLoad = false
$: path && loadTriggers()
const { triggersCount, selectedTrigger, defaultValues } =
getContext<TriggerContext>('TriggerContext')
onMount(() => {
if (
defaultValues &&
$selectedTrigger === 'sqs' &&
Object.keys($defaultValues ?? {}).length > 0
) {
sqsTriggerEditor.openNew(isFlow, path, $defaultValues)
defaultValues.set(undefined)
}
})
let sqsTriggers: (SqsTrigger & { canWrite: boolean })[] | undefined = undefined
export async function loadTriggers() {
try {
sqsTriggers = (
await SqsTriggerService.listSqsTriggers({
workspace: $workspaceStore ?? '',
path,
isFlow
})
).map((x) => {
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
})
$triggersCount = { ...($triggersCount ?? {}), sqs_count: sqsTriggers?.length }
openForm = sqsTriggers?.length === 0 || dontCloseOnLoad
} catch (e) {
console.error('impossible to load SQS triggers', e)
}
}
</script>
<SqsTriggerEditor
on:update={() => {
loadTriggers()
}}
bind:this={sqsTriggerEditor}
/>
{#if !$enterpriseLicense}
<Alert title="EE Only" type="warning" size="xs">
SQS triggers are an enterprise only feature.
</Alert>
{:else if isCloudHosted()}
<Alert title="Not compatible with multi-tenant cloud" type="warning" size="xs">
SQS triggers are disabled in the multi-tenant cloud.
</Alert>
{:else}
<div class="flex flex-col gap-4">
<Description
link="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-how-it-works.html"
>
SQS triggers allow your scripts/flows to process messages from Amazon Simple Queue Service
(SQS) in real time. Each trigger listens to an SQS queue and executes a script or a flow when
new messages arrive.
</Description>
{#if !newItem && sqsTriggers && sqsTriggers.length > 0}
<Section label="SQS">
<div class="flex flex-col gap-4">
<div class="flex flex-col divide-y pt-2">
{#each sqsTriggers as sqsTriggers (sqsTriggers.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{sqsTriggers.path}</div>
<div class="col-span-2 truncate">
{sqsTriggers.queue_url}
</div>
<div class="flex justify-end">
<button
on:click={() => sqsTriggerEditor?.openEdit(sqsTriggers.path, isFlow)}
class="px-2"
>
{#if sqsTriggers.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
</div>
</Section>
{/if}
<TriggersEditorSection
on:applyArgs
on:saveTrigger={(e) => {
sqsTriggerEditor?.openNew(isFlow, path, e.detail.config)
}}
on:addPreprocessor
on:updateSchema
on:testWithArgs
cloudDisabled={false}
triggerType="sqs"
{isFlow}
{path}
{isEditor}
{canHavePreprocessor}
{hasPreprocessor}
{newItem}
{openForm}
bind:showCapture={dontCloseOnLoad}
/>
</div>
{/if}
@@ -158,27 +158,29 @@
{:else}
<div class="flex flex-col w-full gap-4">
<label class="block grow w-full">
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
<div>
URL
<Required required={true} />
<div class="flex flex-col gap-1">
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
<div>
URL
<Required required={true} />
</div>
</div>
<input
type="text"
autocomplete="off"
bind:value={url}
disabled={!can_write}
on:input={() => {
dirtyUrl = true
}}
placeholder="ws://example.com"
class={urlError === ''
? ''
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
/>
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">
{dirtyUrl ? urlError : ''}
</div>
</div>
<input
type="text"
autocomplete="off"
bind:value={url}
disabled={!can_write}
on:input={() => {
dirtyUrl = true
}}
placeholder="ws://example.com"
class={urlError === ''
? ''
: 'border border-red-700 bg-red-100 border-opacity-30 focus:border-red-700 focus:border-opacity-30 focus-visible:ring-red-700 focus-visible:ring-opacity-25 focus-visible:border-red-700'}
/>
<div class="text-red-600 dark:text-red-400 text-2xs mt-1.5">
{dirtyUrl ? urlError : ''}
</div>
</label>
</div>
@@ -274,14 +274,6 @@
</Label>
</div>
<WebsocketEditorConfigSection
bind:url
bind:url_runnable_args
{dirtyUrl}
{can_write}
bind:isValid
/>
<Section label="Runnable" class="flex flex-col gap-4">
<div>
<p class="text-xs mb-1 text-tertiary">
@@ -314,6 +306,14 @@
/>
</Section>
<WebsocketEditorConfigSection
bind:url
bind:url_runnable_args
{dirtyUrl}
{can_write}
bind:isValid
/>
<Section label="Initial messages">
<p class="text-xs mb-1 text-tertiary">
Initial messages are sent at the beginning of the connection. They are sent in order.<br
+120 -89
View File
@@ -579,75 +579,95 @@ export async function main(approver?: string) {
export const BUN_PREPROCESSOR_MODULE_CODE = `
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres',
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
headers: Record<string, string>
},
websocket?: {
url: string // The websocket url
},
kafka?: {
brokers: string[]
topic: string
group_id: string
},
nats?: {
servers: string[]
subject: string
headers?: Record<string, string[]>
status?: number
description?: string
length: number
}
},
/* your other args */
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
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
headers: Record<string, string>
},
websocket?: {
url: string // The websocket url
},
kafka?: {
brokers: string[]
topic: string
group_id: string
},
nats?: {
servers: string[]
subject: string
headers?: Record<string, string[]>
status?: number
description?: string
length: number
},
sqs?: {
queue_url: string,
message_id?: string,
receipt_handle?: string,
attributes: Record<string, string>,
message_attributes?: Record<string, {
string_value?: string,
data_type: string
}>
}
},
/* your other args */
) {
return {
// return the args to be passed to the runnable
}
return {
// return the args to be passed to the runnable
}
}
`
const DENO_PREPROCESSOR_MODULE_CODE = `
export async function preprocessor(
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres',
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
headers: Record<string, string>
},
websocket?: {
url: string // The websocket url
},
kafka?: {
brokers: string[]
topic: string
group_id: string
},
nats?: {
servers: string[]
subject: string
headers?: Record<string, string[]>
status?: number
description?: string
length: number
}
},
/* your other args */
wm_trigger: {
kind: 'http' | 'email' | 'webhook' | 'websocket' | 'kafka' | 'nats' | 'postgres' | 'sqs',
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
headers: Record<string, string>
},
websocket?: {
url: string // The websocket url
},
kafka?: {
brokers: string[]
topic: string
group_id: string
},
nats?: {
servers: string[]
subject: string
headers?: Record<string, string[]>
status?: number
description?: string
length: number
},
sqs?: {
queue_url: string,
message_id?: string,
receipt_handle?: string,
attributes: Record<string, string>,
message_attributes?: Record<string, {
string_value?: string,
data_type: string
}>
}
},
/* your other args */
) {
return {
// return the args to be passed to the runnable
}
return {
// return the args to be passed to the runnable
}
}
`
@@ -679,45 +699,56 @@ def main():
# all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
export const PYTHON_PREPROCESSOR_MODULE_CODE = `from typing import TypedDict, Literal
class Http(TypedDict):
route: str # The route path, e.g. "/users/:id"
path: str # The actual path called, e.g. "/users/123"
method: str
params: dict[str, str]
query: dict[str, str]
headers: dict[str, str]
route: str # The route path, e.g. "/users/:id"
path: str # The actual path called, e.g. "/users/123"
method: str
params: dict[str, str]
query: dict[str, str]
headers: dict[str, str]
class Websocket(TypedDict):
url: str # The websocket url
url: str # The websocket url
class Kafka(TypedDict):
topic: str
brokers: list[str]
group_id: str
topic: str
brokers: list[str]
group_id: str
class Nats(TypedDict):
servers: list[str]
subject: str
headers: dict[str, list[str]] | None
status: int | None
description: str | None
length: int
servers: list[str]
subject: str
headers: dict[str, list[str]] | None
status: int | None
description: str | None
length: int
class MessageAttribute(TypedDict):
string_value: str | None
data_type: str
class Sqs(TypedDict):
queue_url: str
message_id: str | None
receipt_handle: str | None
attributes: dict[str, str]
message_attributes: dict[str, MessageAttribute] | None
class WmTrigger(TypedDict):
kind: Literal["http", "email", "webhook", "websocket", "kafka", "nats", "postgres"]
http: Http | None
websocket: Websocket | None
kafka: Kafka | None
nats: Nats | None
kind: Literal["http", "email", "webhook", "websocket", "kafka", "nats", "postgres", "sqs"]
http: Http | None
websocket: Websocket | None
kafka: Kafka | None
nats: Nats | None
sqs: Sqs | None
def preprocessor(
wm_trigger: WmTrigger,
# your other args
wm_trigger: WmTrigger,
# your other args
):
return {
# return the args to be passed to the runnable
}
return {
# return the args to be passed to the runnable
}
`
const DOCKER_INIT_CODE = `# shellcheck shell=bash
@@ -191,10 +191,12 @@
async function loadUsedTriggerKinds() {
let usedKinds: string[] = []
const { http_routes_used, websocket_used, kafka_used, postgres_used, nats_used } =
await WorkspaceService.getUsedTriggers({
workspace: $workspaceStore ?? ''
})
const { http_routes_used, websocket_used, kafka_used, postgres_used, nats_used , sqs_used} =
await WorkspaceService.getUsedTriggers(
{
workspace: $workspaceStore ?? ''
}
)
if (http_routes_used) {
usedKinds.push('http')
}
@@ -210,6 +212,9 @@
if (nats_used) {
usedKinds.push('nats')
}
if (sqs_used) {
usedKinds.push('sqs')
}
$usedTriggerKinds = usedKinds
}
@@ -66,6 +66,7 @@
import NatsTriggersPanel from '$lib/components/triggers/nats/NatsTriggersPanel.svelte'
import PostgresTriggersPanel from '$lib/components/triggers/postgres/PostgresTriggersPanel.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import SqsTriggerPanel from '$lib/components/triggers/sqs/SqsTriggerPanel.svelte'
let flow: Flow | undefined
let can_write = false
@@ -586,12 +587,19 @@
<PostgresTriggersPanel path={flow.path ?? ''} isFlow />
</div>
</svelte:fragment>
<svelte:fragment slot="nats">
<div class="p-2">
<NatsTriggersPanel path={flow.path ?? ''} isFlow />
</div>
</svelte:fragment>
<svelte:fragment slot="sqs">
<div class="p-2">
<SqsTriggerPanel path={flow.path ?? ''} isFlow />
</div>
</svelte:fragment>
<svelte:fragment slot="emails">
<div class="p-2">
<EmailTriggerPanel
@@ -89,6 +89,7 @@
import PostgresTriggersPanel from '$lib/components/triggers/postgres/PostgresTriggersPanel.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import InputSelectedBadge from '$lib/components/schema/InputSelectedBadge.svelte'
import SqsTriggerPanel from '$lib/components/triggers/sqs/SqsTriggerPanel.svelte'
let script: Script | undefined
let topHash: string | undefined
@@ -757,21 +758,26 @@
<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="postgres">
<div class="p-2">
<PostgresTriggersPanel 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="nats">
<div class="p-2">
<NatsTriggersPanel path={script.path ?? ''} isFlow={false} />
</div>
</svelte:fragment>
<svelte:fragment slot="sqs">
<div class="p-2">
<SqsTriggerPanel path={script.path ?? ''} isFlow={false} />
</div>
</svelte:fragment>
<svelte:fragment slot="emails">
<div class="p-2">
<EmailTriggerPanel
@@ -0,0 +1,5 @@
export function load() {
return {
stuff: { title: 'SQS triggers' }
}
}
@@ -0,0 +1,422 @@
<script lang="ts">
import { SqsTriggerService, type SqsTrigger } 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, Database } 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 Popover from '$lib/components/Popover.svelte'
import { isCloudHosted } from '$lib/cloud'
import SqsTriggerEditor from '$lib/components/triggers/sqs/SqsTriggerEditor.svelte'
type TriggerD = SqsTrigger & { canWrite: boolean }
let triggers: TriggerD[] = []
let shareModal: ShareModal
let loading = true
async function loadTriggers(): Promise<void> {
triggers = (await SqsTriggerService.listSqsTriggers({ workspace: $workspaceStore! })).map(
(x) => {
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
}
)
loading = false
}
let interval = setInterval(async () => {
try {
const newTriggers = await SqsTriggerService.listSqsTriggers({
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,
server_id: newTrigger.server_id
}
}
}
} catch (err) {
console.error(err)
}
}, 5000)
onDestroy(() => {
clearInterval(interval)
})
async function setTriggerEnabled(path: string, enabled: boolean): Promise<void> {
try {
await SqsTriggerService.setSqsTriggerEnabled({
path,
workspace: $workspaceStore!,
requestBody: { enabled }
})
} catch (err) {
sendUserToast(
`Cannot ` + (enabled ? 'enable' : 'disable') + ` sqs trigger: ${err.body}`,
true
)
} finally {
loadTriggers()
}
}
$: {
if ($workspaceStore && $userStore) {
loadTriggers()
}
}
let sqsTriggerEditor: SqsTriggerEditor
let filteredItems: (TriggerD & { 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: TriggerD,
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>
<SqsTriggerEditor on:update={loadTriggers} bind:this={sqsTriggerEditor} />
<SearchItems
{filter}
items={preFilteredItems}
bind:filteredItems
f={(x) => (x.summary ?? '') + ' ' + x.path + ' (' + x.script_path + ')'}
/>
<CenteredPage>
<PageHeader title="SQS triggers" tooltip="SQS trigger">
<Button size="md" startIcon={{ icon: Plus }} on:click={() => sqsTriggerEditor.openNew(false)}>
New&nbsp;SQS trigger
</Button>
</PageHeader>
{#if isCloudHosted()}
<Alert title="Not compatible with multi-tenant cloud" type="warning">
SQS 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 SQS 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="SQS trigger" icon={Database} />
<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 sqs triggers </div>
{:else if items?.length}
<div class="border rounded-md divide-y">
{#each items.slice(0, nbDisplayed) as { path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, enabled, server_id } (path)}
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
{@const ping = new Date()}
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
<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={() => sqsTriggerEditor?.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">
{path}
</div>
<div class="text-secondary text-xs truncate text-left font-light" />
<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 && (!pinging || error)) || (!enabled && error) || (enabled && !server_id)}
<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}
{#if !server_id}
SQS trigger is starting...
{:else}
Could not connect to SQS{error ? ': ' + error : ''}
{/if}
{:else}
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">
Connected to sqs{!server_id ? ' (shutting down...)' : ''}</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={() => sqsTriggerEditor?.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 () => {
try {
await SqsTriggerService.deleteSqsTrigger({
workspace: $workspaceStore ?? '',
path
})
loadTriggers()
} catch (error) {
sendUserToast(error.body, true)
}
}
},
{
displayName: canWrite ? 'Edit' : 'View',
icon: canWrite ? Pen : Eye,
action: () => {
sqsTriggerEditor?.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, 'websocket_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()
}}
/>