feat: allow http trigger to be disabled (#6976)

* done

* update sqlx

* fix

* fix openapi spec

* fix

* Update backend/windmill-api/openapi.yaml

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

* nits

* nits

* fix

* fix

* update sqlx

* unneeded

* ok

* update sqlx

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
dieriba
2025-11-17 08:51:35 +00:00
committed by GitHub
co-authored by ellipsis-dev[bot]
parent 65a40b6df1
commit 09082de539
15 changed files with 192 additions and 89 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19, $20, $21, $22\n )\n ",
"query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n enabled,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ",
"describe": {
"columns": [],
"parameters": {
@@ -17,6 +17,7 @@
"Varchar",
"Text",
"Bool",
"Bool",
{
"Custom": {
"name": "request_type",
@@ -69,5 +70,5 @@
},
"nullable": []
},
"hash": "1f6b773ce34fe51d03d6f9a2345481629c62453eebbb08f82dd2da23389bc117"
"hash": "94e2e899fc0a5134c29217feda1f0530d03548cc5c884c83d7b85d068e738b8e"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n http_method = $10,\n static_asset_config = $11,\n edited_by = $12,\n email = $13,\n request_type = $14,\n authentication_method = $15,\n summary = $16,\n description = $17,\n edited_at = now(),\n is_static_website = $18,\n error_handler_path = $19,\n error_handler_args = $20,\n retry = $21\n WHERE\n workspace_id = $22 AND\n path = $23\n ",
"query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n enabled = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n email = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ",
"describe": {
"columns": [],
"parameters": {
@@ -14,6 +14,7 @@
"Varchar",
"Varchar",
"Bool",
"Bool",
{
"Custom": {
"name": "http_method",
@@ -70,5 +71,5 @@
},
"nullable": []
},
"hash": "465144ea7e2930203618d9814a3e20c77b4363cf9e7c655d395f3fe40c247f61"
"hash": "b8bcf1fef244395dad802174dfc9bff45ff6f9cd1298eae5de40218cbab2eb96"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1\n ",
"query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n enabled is TRUE\n ",
"describe": {
"columns": [
{
@@ -158,5 +158,5 @@
true
]
},
"hash": "1301f873a829db137573b8b39449f6160f2adf44f864f26a99b8eab5818fbd50"
"hash": "bbc96ae911d4ca0330582340e65c10e008e507610f095ff3936865101c9ba346"
}
@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE http_trigger DROP COLUMN enabled;
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE http_trigger ADD COLUMN enabled BOOLEAN DEFAULT TRUE NOT NULL;
+33 -22
View File
@@ -10163,6 +10163,34 @@ paths:
schema:
type: boolean
/w/{workspace}/http_triggers/setenabled/{path}:
post:
summary: enable/disable http trigger
operationId: setHttpTriggerEnabled
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
required:
- enabled
responses:
"200":
description: http trigger enable/disable
content:
text/plain:
schema:
type: string
/w/{workspace}/websocket_triggers/create:
post:
summary: create websocket trigger
@@ -17222,6 +17250,8 @@ components:
format: date-time
is_flow:
type: boolean
enabled:
type: boolean
required:
- path
- script_path
@@ -17231,6 +17261,7 @@ components:
- edited_by
- edited_at
- is_flow
- enabled
AuthenticationMethod:
type: string
@@ -17456,6 +17487,8 @@ components:
type: boolean
wrap_body:
type: boolean
enabled:
type: boolean
raw_string:
type: boolean
error_handler_path:
@@ -17581,8 +17614,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
filters:
type: array
items:
@@ -17613,7 +17644,6 @@ components:
required:
- url
- enabled
- filters
- can_return_message
- can_return_error_result
@@ -17802,17 +17832,13 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
$ref: "#/components/schemas/ScriptArgs"
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
required:
- enabled
- subscribe_topics
- mqtt_resource_path
@@ -17934,8 +17960,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -17946,7 +17970,6 @@ components:
- gcp_resource_path
- topic_id
- subscription_id
- enabled
- delivery_type
- subscription_mode
@@ -18048,8 +18071,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -18060,7 +18081,6 @@ components:
required:
- queue_url
- aws_resource_path
- enabled
- aws_auth_resource_type
LoggedWizardStatus:
@@ -18257,8 +18277,6 @@ components:
- $ref: "#/components/schemas/TriggerExtraProperty"
type: object
properties:
enabled:
type: boolean
postgres_resource_path:
type: string
publication_name:
@@ -18279,7 +18297,6 @@ components:
retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
required:
- enabled
- postgres_resource_path
- replication_slot_name
- publication_name
@@ -18370,8 +18387,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -18383,7 +18398,6 @@ components:
- kafka_resource_path
- group_id
- topics
- enabled
NewKafkaTrigger:
type: object
@@ -18475,8 +18489,6 @@ components:
format: date-time
error:
type: string
enabled:
type: boolean
error_handler_path:
type: string
error_handler_args:
@@ -18488,7 +18500,6 @@ components:
- nats_resource_path
- use_jetstream
- subjects
- enabled
NewNatsTrigger:
type: object
+66 -33
View File
@@ -146,8 +146,12 @@ pub trait TriggerCrud: Send + Sync + 'static {
"extra_perms",
];
if Self::SUPPORTS_ENABLED {
fields.push("enabled");
}
if Self::SUPPORTS_SERVER_STATE {
fields.extend_from_slice(&["enabled", "server_id", "last_server_ping", "error"]);
fields.extend_from_slice(&["server_id", "last_server_ping", "error"]);
}
fields.extend_from_slice(&["error_handler_path", "error_handler_args", "retry"]);
@@ -206,6 +210,10 @@ pub trait TriggerCrud: Send + Sync + 'static {
Ok(deleted > 0)
}
async fn set_enabled_extra_action(&self, _: &mut PgConnection) -> Result<()> {
Ok(())
}
async fn set_enabled(
&self,
authed: &ApiAuthed,
@@ -214,38 +222,59 @@ pub trait TriggerCrud: Send + Sync + 'static {
path: &str,
enabled: bool,
) -> Result<bool> {
if !Self::SUPPORTS_SERVER_STATE {
return Err(anyhow::anyhow!(
"Enable/disable not supported for this trigger type".to_string(),
)
.into());
}
let updated = if Self::SUPPORTS_SERVER_STATE {
sqlx::query(&format!(
r#"
UPDATE
{}
SET
enabled = $1,
email = $2,
edited_by = $3,
edited_at = now(),
server_id = NULL,
error = NULL
WHERE
workspace_id = $4 AND
path = $5
"#,
Self::TABLE_NAME
))
.bind(enabled)
.bind(&authed.email)
.bind(&authed.username)
.bind(workspace_id)
.bind(path)
.execute(&mut *tx)
.await?
.rows_affected()
} else {
sqlx::query(&format!(
r#"
UPDATE
{}
SET
enabled = $1,
email = $2,
edited_by = $3,
edited_at = now()
WHERE
workspace_id = $4 AND
path = $5
"#,
Self::TABLE_NAME
))
.bind(enabled)
.bind(&authed.email)
.bind(&authed.username)
.bind(workspace_id)
.bind(path)
.execute(&mut *tx)
.await?
.rows_affected()
};
let updated = sqlx::query(&format!(
r#"
UPDATE
{}
SET
enabled = $1,
email = $2,
edited_by = $3,
edited_at = now(),
server_id = NULL,
error = NULL
WHERE
workspace_id = $4 AND
path = $5
"#,
Self::TABLE_NAME
))
.bind(enabled)
.bind(&authed.email)
.bind(&authed.username)
.bind(workspace_id)
.bind(path)
.execute(&mut *tx)
.await?
.rows_affected();
self.set_enabled_extra_action(&mut *tx).await?;
Ok(updated > 0)
}
@@ -298,8 +327,12 @@ pub trait TriggerCrud: Send + Sync + 'static {
"extra_perms",
];
if Self::SUPPORTS_ENABLED {
fields.push("enabled");
}
if Self::SUPPORTS_SERVER_STATE {
fields.extend_from_slice(&["enabled", "server_id", "last_server_ping", "error"]);
fields.extend_from_slice(&["server_id", "last_server_ping", "error"]);
}
fields.extend_from_slice(&["error_handler_path", "error_handler_args", "retry"]);
@@ -196,6 +196,7 @@ pub async fn insert_new_trigger_into_db(
summary,
description,
is_flow,
enabled,
request_type,
authentication_method,
http_method,
@@ -209,7 +210,7 @@ pub async fn insert_new_trigger_into_db(
retry
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, now(), $19, $20, $21, $22
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23
)
"#,
w_id,
@@ -224,6 +225,7 @@ pub async fn insert_new_trigger_into_db(
trigger.config.summary,
trigger.config.description,
trigger.base.is_flow,
trigger.base.enabled.unwrap_or(true),
request_type as _,
trigger.config.authentication_method as _,
trigger.config.http_method as _,
@@ -356,7 +358,7 @@ impl TriggerCrud for HttpTrigger {
const TABLE_NAME: &'static str = "http_trigger";
const TRIGGER_TYPE: &'static str = "http";
const SUPPORTS_ENABLED: bool = false;
const SUPPORTS_ENABLED: bool = true;
const SUPPORTS_SERVER_STATE: bool = false;
const SUPPORTS_TEST_CONNECTION: bool = false;
const ROUTE_PREFIX: &'static str = "/http_triggers";
@@ -482,22 +484,23 @@ impl TriggerCrud for HttpTrigger {
script_path = $7,
path = $8,
is_flow = $9,
http_method = $10,
static_asset_config = $11,
edited_by = $12,
email = $13,
request_type = $14,
authentication_method = $15,
summary = $16,
description = $17,
enabled = $10,
http_method = $11,
static_asset_config = $12,
edited_by = $13,
email = $14,
request_type = $15,
authentication_method = $16,
summary = $17,
description = $18,
edited_at = now(),
is_static_website = $18,
error_handler_path = $19,
error_handler_args = $20,
retry = $21
is_static_website = $19,
error_handler_path = $20,
error_handler_args = $21,
retry = $22
WHERE
workspace_id = $22 AND
path = $23
workspace_id = $23 AND
path = $24
"#,
route_path,
&route_path_key,
@@ -508,6 +511,7 @@ impl TriggerCrud for HttpTrigger {
trigger.base.script_path,
trigger.base.path,
trigger.base.is_flow,
trigger.base.enabled.unwrap_or(true),
trigger.config.http_method as _,
trigger.config.static_asset_config as _,
&authed.username,
@@ -586,6 +590,10 @@ impl TriggerCrud for HttpTrigger {
Ok(())
}
async fn set_enabled_extra_action(&self, tx: &mut PgConnection) -> Result<()> {
increase_trigger_version(tx).await
}
async fn delete_by_path(
&self,
tx: &mut PgConnection,
@@ -261,7 +261,8 @@ pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, Route
FROM
http_trigger
WHERE
http_method = $1
http_method = $1 AND
enabled is TRUE
"#,
&http_method as &HttpMethod
)
@@ -69,12 +69,12 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
"email",
"edited_at",
"extra_perms",
"enabled",
"error_handler_path",
"error_handler_args",
"retry",
];
if Self::SUPPORTS_SERVER_STATE {
fields.extend_from_slice(&["enabled", "server_id", "last_server_ping", "error"]);
}
fields.extend_from_slice(&["error_handler_path", "error_handler_args", "retry"]);
fields.extend_from_slice(Self::ADDITIONAL_SELECT_FIELDS);
let mut sqlb = SqlBuilder::select_from(Self::TABLE_NAME);
+1 -1
View File
@@ -47,6 +47,7 @@ pub struct BaseTrigger {
pub workspace_id: String,
pub path: String,
pub script_path: String,
pub enabled: Option<bool>,
pub is_flow: bool,
pub edited_by: String,
pub email: String,
@@ -56,7 +57,6 @@ pub struct BaseTrigger {
#[derive(Debug, FromRow, Clone, Serialize, Deserialize)]
pub struct ServerState {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -350,14 +350,14 @@ pub async fn get_raw_postgres_connection(
}
let connector = build_tls_connector(ssl_mode, database.root_certificate_pem.as_ref())?;
let client = if let Some(connector) = connector {
let (client, connection) = config.connect(connector).await.map_err(to_anyhow)?;
tokio::spawn(async move {
tracing::info!("Successfully connected to PostgreSQL database for trigger execution");
if let Err(e) = connection.await {
tracing::debug!("{:#?}", e);
tracing::debug!("Error during PostgreSQL trigger connection: {:#?}", e);
};
tracing::info!("Successfully Connected into database");
tracing::info!("PostgreSQL trigger connection closed");
});
client
} else {
@@ -83,6 +83,7 @@
let static_asset_config = $state<{ s3: string; storage?: string; filename?: string } | undefined>(
undefined
)
let enabled: boolean = $state(false)
let is_static_website = $state(false)
let s3FileUploadRawMode = $state(false)
let workspaced_route = $state(false)
@@ -235,6 +236,7 @@
s3FileUploadRawMode = defaultValues?.s3FileUploadRawMode ?? false
path = defaultValues?.path ?? ''
initialPath = ''
enabled = defaultValues?.enabled ?? true
dirtyPath = false
is_static_website = defaultValues?.is_static_website ?? false
workspaced_route = defaultValues?.workspaced_route ?? false
@@ -268,6 +270,7 @@
wrap_body = cfg?.wrap_body ?? false
raw_string = cfg?.raw_string ?? false
summary = cfg?.summary ?? ''
enabled = cfg?.enabled ?? false
routeDescription = cfg?.description ?? ''
authentication_resource_path = cfg?.authentication_resource_path ?? ''
if (cfg?.authentication_method === 'custom_script') {
@@ -344,6 +347,7 @@
http_method,
request_type,
workspaced_route,
enabled,
wrap_body,
raw_string,
authentication_resource_path,
@@ -361,6 +365,19 @@
return nCfg
}
async function handleToggleEnabled(newEnabled: boolean) {
enabled = newEnabled
if (!trigger?.draftConfig) {
await HttpTriggerService.setHttpTriggerEnabled({
path: initialPath,
workspace: $workspaceStore ?? '',
requestBody: { enabled: newEnabled }
})
sendUserToast(`${newEnabled ? 'enabled' : 'disabled'} HTTP trigger ${initialPath}`)
}
}
// Update config for captures
function getCaptureConfig() {
const newCaptureConfig = {
@@ -829,7 +846,8 @@
{trigger}
permissions={drawerLoading || !can_write ? 'none' : can_write && isAdmin ? 'create' : 'write'}
{saveDisabled}
enabled={undefined}
{enabled}
onToggleEnabled={handleToggleEnabled}
{allowDraft}
{edit}
isLoading={deploymentLoading}
@@ -60,7 +60,8 @@ export async function saveHttpRouteFromCfg(
summary: routeCfg.summary,
error_handler_path: routeCfg.error_handler_path,
error_handler_args: routeCfg.error_handler_path ? routeCfg.error_handler_args : undefined,
retry: routeCfg.retry
retry: routeCfg.retry,
enabled: routeCfg.enabled
}
try {
if (edit) {
@@ -196,6 +196,23 @@
}
}
async function setTriggerEnabled(path: string, enabled: boolean): Promise<void> {
try {
await HttpTriggerService.setHttpTriggerEnabled({
path,
workspace: $workspaceStore!,
requestBody: { enabled }
})
} catch (err) {
sendUserToast(
`Cannot ` + (enabled ? 'enable' : 'disable') + ` http trigger: ${err.body}`,
true
)
} finally {
loadTriggers()
}
}
onMount(() => {
loadQueryFilters()
})
@@ -297,7 +314,7 @@
<div class="text-center text-sm font-semibold text-emphasis mt-2"> No routes </div>
{:else if items?.length}
<div class="border rounded-md divide-y">
{#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_route, path, edited_by, edited_at, script_path, route_path, is_flow, extra_perms, canWrite, marked, http_method, static_asset_config } (path)}
{#each items.slice(0, nbDisplayed) as { workspace_id, workspaced_route, enabled, path, edited_by, edited_at, script_path, route_path, is_flow, extra_perms, canWrite, marked, http_method, static_asset_config } (path)}
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
<div
@@ -342,6 +359,14 @@
<SharedBadge {canWrite} extraPerms={extra_perms} />
</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={() =>