mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
feat: Add override all schedule handlers button (#2579)
* feat: Add override all schedule handlers button * sqlx prepare * sqlx prepare again
This commit is contained in:
committed by
GitHub
parent
18d04220e6
commit
f2bff84502
-1
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -67,7 +67,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -28,7 +28,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET ws_error_handler_muted = $1, on_failure = $2, on_failure_extra_args = $3, on_failure_times = $4, on_failure_exact = $5 WHERE workspace_id = $6",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Json",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "539d9ae486c7d1f5fb8c7278d698675b04359182b781b0cb0164402b798a7fef"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE schedule SET on_recovery = $1, on_recovery_extra_args = $2, on_recovery_times = $3 WHERE workspace_id = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Json",
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "542ebd3d6cd8522d117112ef9eb13ff95b93d67994823a4a84d37639eb17e0b9"
|
||||
}
|
||||
-1
@@ -60,7 +60,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -40,7 +40,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -46,7 +46,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -42,7 +42,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
-1
@@ -37,7 +37,6 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
version: 1.200.0
|
||||
version: 1.201.0
|
||||
title: Windmill API
|
||||
contact:
|
||||
name: Windmill Team
|
||||
@@ -875,6 +875,19 @@ paths:
|
||||
'text/plain:':
|
||||
schema:
|
||||
type: string
|
||||
/settings/send_stats:
|
||||
post:
|
||||
summary: send stats
|
||||
operationId: sendStats
|
||||
tags:
|
||||
- setting
|
||||
responses:
|
||||
'200':
|
||||
description: status
|
||||
content:
|
||||
'text/plain:':
|
||||
schema:
|
||||
type: string
|
||||
/users/email:
|
||||
get:
|
||||
summary: get current user email (if logged in)
|
||||
@@ -8150,6 +8163,49 @@ paths:
|
||||
- id
|
||||
- success
|
||||
- duration_ms
|
||||
/w/{workspace}/schedules/setdefaulthandler:
|
||||
post:
|
||||
summary: Set default error or recoevery handler
|
||||
operationId: setDefaultErrorOrRecoveryHandler
|
||||
tags:
|
||||
- schedule
|
||||
parameters:
|
||||
- name: workspace
|
||||
in: path
|
||||
required: true
|
||||
schema: *ref_0
|
||||
requestBody:
|
||||
description: Handler description
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
handler_type:
|
||||
type: string
|
||||
enum:
|
||||
- error
|
||||
- recovery
|
||||
override_existing:
|
||||
type: boolean
|
||||
path:
|
||||
type: string
|
||||
extra_args:
|
||||
type: object
|
||||
number_of_occurence:
|
||||
type: integer
|
||||
number_of_occurence_exact:
|
||||
type: boolean
|
||||
workspace_handler_muted:
|
||||
type: boolean
|
||||
required:
|
||||
- handler_type
|
||||
- override_existing
|
||||
- path
|
||||
responses:
|
||||
'201':
|
||||
description: default error handler set
|
||||
/groups/list:
|
||||
get:
|
||||
summary: list instance groups
|
||||
|
||||
@@ -5298,6 +5298,45 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/ScheduleWJobs"
|
||||
|
||||
/w/{workspace}/schedules/setdefaulthandler:
|
||||
post:
|
||||
summary: Set default error or recoevery handler
|
||||
operationId: setDefaultErrorOrRecoveryHandler
|
||||
tags:
|
||||
- schedule
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: Handler description
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
handler_type:
|
||||
type: string
|
||||
enum: ["error", "recovery"]
|
||||
override_existing:
|
||||
type: boolean
|
||||
path:
|
||||
type: string
|
||||
extra_args:
|
||||
type: object
|
||||
number_of_occurence:
|
||||
type: integer
|
||||
number_of_occurence_exact:
|
||||
type: boolean
|
||||
workspace_handler_muted:
|
||||
type: boolean
|
||||
required:
|
||||
- handler_type
|
||||
- override_existing
|
||||
- path
|
||||
responses:
|
||||
"201":
|
||||
description: default error handler set
|
||||
|
||||
/groups/list:
|
||||
get:
|
||||
summary: list instance groups
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
settings::set_global_setting_internal,
|
||||
users::maybe_refresh_folders,
|
||||
utils::require_super_admin,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -40,6 +42,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/update/*path", post(edit_schedule))
|
||||
.route("/delete/*path", delete(delete_schedule))
|
||||
.route("/setenabled/*path", post(set_enabled))
|
||||
.route("/setdefaulthandler", post(set_default_error_handler))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -65,6 +68,18 @@ pub struct NewSchedule {
|
||||
pub ws_error_handler_muted: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ErrorOrRecoveryHandler {
|
||||
pub handler_type: String, // 'error' or 'recovery'
|
||||
pub override_existing: bool,
|
||||
|
||||
pub path: String,
|
||||
pub extra_args: Option<serde_json::Value>,
|
||||
pub number_of_occurence: Option<i32>,
|
||||
pub number_of_occurence_exact: Option<bool>,
|
||||
pub workspace_handler_muted: Option<bool>,
|
||||
}
|
||||
|
||||
async fn check_path_conflict<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
w_id: &str,
|
||||
@@ -486,6 +501,77 @@ async fn delete_schedule(
|
||||
Ok(format!("schedule {} deleted", path))
|
||||
}
|
||||
|
||||
async fn set_default_error_handler(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(payload): Json<ErrorOrRecoveryHandler>,
|
||||
) -> Result<()> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let (key, value) = match payload.handler_type.as_str() {
|
||||
"error" => {
|
||||
let key = format!("default_error_handler_{}", w_id);
|
||||
let value = serde_json::json!({
|
||||
"wsErrorHandlerMuted": payload.workspace_handler_muted,
|
||||
"errorHandlerPath": payload.path,
|
||||
"errorHandlerExtraArgs": payload.extra_args,
|
||||
"failedTimes": payload.number_of_occurence,
|
||||
"failedExact": payload.number_of_occurence_exact,
|
||||
});
|
||||
Ok((key, value))
|
||||
}
|
||||
"recovery" => {
|
||||
let key = format!("default_recovery_handler_{}", w_id);
|
||||
let value = serde_json::json!({
|
||||
"recoveryHandlerPath": payload.path,
|
||||
"recoveryHandlerExtraArgs": payload.extra_args,
|
||||
"recoveredTimes": payload.number_of_occurence,
|
||||
});
|
||||
Ok((key, value))
|
||||
}
|
||||
_ => Err(Error::BadRequest(
|
||||
"handler_type must be either 'error' or 'recovery'".to_string(),
|
||||
)),
|
||||
}?;
|
||||
|
||||
set_global_setting_internal(&db, key, value).await?;
|
||||
|
||||
if payload.override_existing {
|
||||
match payload.handler_type.as_str() {
|
||||
"error" => {
|
||||
sqlx::query!(
|
||||
"UPDATE schedule SET ws_error_handler_muted = $1, on_failure = $2, on_failure_extra_args = $3, on_failure_times = $4, on_failure_exact = $5 WHERE workspace_id = $6",
|
||||
payload.workspace_handler_muted,
|
||||
payload.path,
|
||||
payload.extra_args,
|
||||
payload.number_of_occurence,
|
||||
payload.number_of_occurence_exact,
|
||||
w_id,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
"recovery" => {
|
||||
sqlx::query!(
|
||||
"UPDATE schedule SET on_recovery = $1, on_recovery_extra_args = $2, on_recovery_times = $3 WHERE workspace_id = $4",
|
||||
payload.path,
|
||||
payload.extra_args,
|
||||
payload.number_of_occurence,
|
||||
w_id,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::BadRequest(
|
||||
"handler_type must be either 'error' or 'recovery'".to_string(),
|
||||
)),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_flow_conflict<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
w_id: &str,
|
||||
|
||||
@@ -125,25 +125,32 @@ pub async fn set_global_setting(
|
||||
Json(value): Json<Value>,
|
||||
) -> error::Result<()> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
match value.value {
|
||||
set_global_setting_internal(&db, key, value.value).await
|
||||
}
|
||||
|
||||
pub async fn set_global_setting_internal(
|
||||
db: &DB,
|
||||
key: String,
|
||||
value: serde_json::Value,
|
||||
) -> error::Result<()> {
|
||||
match value {
|
||||
serde_json::Value::Null => {
|
||||
delete_global_setting(&db, &key).await?;
|
||||
delete_global_setting(db, &key).await?;
|
||||
}
|
||||
serde_json::Value::String(x) if x.is_empty() => {
|
||||
delete_global_setting(&db, &key).await?;
|
||||
delete_global_setting(db, &key).await?;
|
||||
}
|
||||
v => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
|
||||
key,
|
||||
v
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
"INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()",
|
||||
key,
|
||||
v
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
tracing::info!("Set global setting {} to {}", key, v);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1468,12 +1468,6 @@ pub fn is_none_or_false(val: &Option<bool>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_none_or_empty(val: &Option<bool>) -> bool {
|
||||
match val {
|
||||
Some(val) => !val,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
enum ArchiveImpl {
|
||||
Zip(async_zip::write::ZipFileWriter<File>),
|
||||
Tar(tokio_tar::Builder<File>),
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import ErrorOrRecoveryHandler from '$lib/components/ErrorOrRecoveryHandler.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import {
|
||||
FlowService,
|
||||
ScheduleService,
|
||||
@@ -21,6 +22,7 @@
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, formatCron, sendUserToast } from '$lib/utils'
|
||||
import { faList, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Save } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
|
||||
@@ -164,42 +166,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAsDefaultErrorHandler() {
|
||||
async function saveAsDefaultErrorHandler(overrideExisting: boolean) {
|
||||
if (!$enterpriseLicense) {
|
||||
sendUserToast(`Setting default error handler is an enterprise edition feature`, true)
|
||||
return
|
||||
}
|
||||
if ($workspaceStore && errorHandlerPath !== undefined) {
|
||||
await SettingService.setGlobal({
|
||||
key: 'default_error_handler_' + $workspaceStore!,
|
||||
await ScheduleService.setDefaultErrorOrRecoveryHandler({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: {
|
||||
wsErrorHandlerMuted: wsErrorHandlerMuted,
|
||||
errorHandlerPath: `${errorHandleritemKind}/${errorHandlerPath}`,
|
||||
errorHandlerExtraArgs: errorHandlerExtraArgs,
|
||||
failedTimes: failedTimes,
|
||||
failedExact: failedExact
|
||||
}
|
||||
handler_type: 'error',
|
||||
override_existing: overrideExisting,
|
||||
path: `${errorHandleritemKind}/${errorHandlerPath}`,
|
||||
extra_args: errorHandlerExtraArgs,
|
||||
number_of_occurence: failedTimes,
|
||||
number_of_occurence_exact: failedExact,
|
||||
workspace_handler_muted: wsErrorHandlerMuted
|
||||
}
|
||||
})
|
||||
sendUserToast(`Default error handler saved to ${errorHandlerPath}`, false)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAsDefaultRecoveryHandler() {
|
||||
async function saveAsDefaultRecoveryHandler(overrideExisting: boolean) {
|
||||
if (!$enterpriseLicense) {
|
||||
sendUserToast(`Setting default recovery handler is an enterprise edition feature`, true)
|
||||
return
|
||||
}
|
||||
if ($workspaceStore && errorHandlerPath !== undefined) {
|
||||
await SettingService.setGlobal({
|
||||
key: 'default_recovery_handler_' + $workspaceStore!,
|
||||
await ScheduleService.setDefaultErrorOrRecoveryHandler({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: {
|
||||
recoveryHandlerPath: `${recoveryHandlerItemKind}/${recoveryHandlerPath}`,
|
||||
recoveryHandlerExtraArgs: recoveryHandlerExtraArgs,
|
||||
recoveredTimes: recoveredTimes
|
||||
}
|
||||
handler_type: 'recovery',
|
||||
override_existing: overrideExisting,
|
||||
path: `${recoveryHandlerItemKind}/${recoveryHandlerPath}`,
|
||||
extra_args: recoveryHandlerExtraArgs,
|
||||
number_of_occurence: recoveredTimes
|
||||
}
|
||||
})
|
||||
sendUserToast(`Default recovery handler saved to ${errorHandlerPath}`, false)
|
||||
@@ -430,16 +432,28 @@
|
||||
<Section label="Error handler">
|
||||
<svelte:fragment slot="action">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Button
|
||||
disabled={emptyString(errorHandlerPath)}
|
||||
btnClasses="text-center"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={saveAsDefaultErrorHandler}
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
name="Save as default"
|
||||
dropdownItems={[
|
||||
{
|
||||
disabled: emptyString(errorHandlerPath),
|
||||
displayName: `Future schedules only`,
|
||||
action: () => saveAsDefaultErrorHandler(false)
|
||||
},
|
||||
{
|
||||
disabled: emptyString(errorHandlerPath),
|
||||
displayName: 'Override all existing',
|
||||
type: 'delete',
|
||||
action: () => saveAsDefaultErrorHandler(true)
|
||||
}
|
||||
]}
|
||||
>
|
||||
Save as default
|
||||
</Button>
|
||||
<svelte:fragment>
|
||||
<Save size={12} class="mr-1" />
|
||||
Set as default
|
||||
</svelte:fragment>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-row">
|
||||
@@ -519,16 +533,28 @@
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="action">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Button
|
||||
disabled={emptyString(recoveryHandlerPath)}
|
||||
btnClasses="text-center"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={saveAsDefaultRecoveryHandler}
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
name="Save as default"
|
||||
dropdownItems={[
|
||||
{
|
||||
disabled: emptyString(errorHandlerPath),
|
||||
displayName: `Future schedules only`,
|
||||
action: () => saveAsDefaultRecoveryHandler(false)
|
||||
},
|
||||
{
|
||||
disabled: emptyString(errorHandlerPath),
|
||||
displayName: 'Override all existing',
|
||||
type: 'delete',
|
||||
action: () => saveAsDefaultRecoveryHandler(true)
|
||||
}
|
||||
]}
|
||||
>
|
||||
Save as default
|
||||
</Button>
|
||||
<svelte:fragment>
|
||||
<Save size={12} class="mr-1" />
|
||||
Set as default
|
||||
</svelte:fragment>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user