nits and fix

This commit is contained in:
dieriba
2025-11-15 00:06:52 +01:00
parent 453031cc8a
commit 7d91fa2280
15 changed files with 282 additions and 89 deletions

View File

@@ -1 +1 @@
3e7a82db7a8716d76557c4994a79910aaf4d765c
a01942048c2464317fc0484c0b2414e14aa5f2a4

View File

@@ -8582,21 +8582,28 @@ paths:
items:
type: string
/w/{workspace}/jobs/queue/resume_suspended:
/w/{workspace}/trigger/{trigger_kind}/resume_suspended_trigger_job/{trigger_path}:
post:
summary: resume all suspended jobs with the given suspend number
operationId: resumeSuspendedJobs
summary: resume all suspended jobs for a specific trigger
operationId: resumeSuspendedTriggerJobs
tags:
- job
- trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: suspend number of the jobs to resume
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SuspendedJobRequest"
- name: trigger_kind
description: The kind of trigger
in: path
required: true
schema:
$ref: "#/components/schemas/JobTriggerKind"
- name: trigger_path
description: The path of the trigger (can contain forward slashes)
in: path
required: true
schema:
type: string
style: simple
explode: false
responses:
"200":
description: confirmation message
@@ -8605,21 +8612,28 @@ paths:
schema:
type: string
/w/{workspace}/jobs/queue/cancel_suspended:
/w/{workspace}/trigger/{trigger_kind}/cancel_suspended_trigger_job/{trigger_path}:
post:
summary: cancel all suspended jobs with the given suspend number
operationId: cancelSuspendedJobs
summary: cancel all suspended jobs for a specific trigger
operationId: cancelSuspendedTriggerJobs
tags:
- job
- trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: suspend number of the jobs to cancel
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SuspendedJobRequest"
- name: trigger_kind
description: The kind of trigger
in: path
required: true
schema:
$ref: "#/components/schemas/JobTriggerKind"
- name: trigger_path
description: The path of the trigger (can contain forward slashes)
in: path
required: true
schema:
type: string
style: simple
explode: false
responses:
"200":
description: confirmation message
@@ -17258,18 +17272,6 @@ components:
- poll
- cli
SuspendedJobRequest:
type: object
properties:
trigger_path:
type: string
description: The path of the trigger
trigger_kind:
$ref: "#/components/schemas/JobTriggerKind"
required:
- runnable_path
- trigger_kind
TriggerExtraProperty:
type: object
properties:

View File

@@ -37,7 +37,7 @@ use windmill_common::jobs::{
DynamicInput, JobTriggerKind, ENTRYPOINT_OVERRIDE,
};
use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
use windmill_common::triggers::TriggerInfo;
use windmill_common::triggers::TriggerMetadata;
use windmill_common::utils::{RunnableKind, WarnAfterExt};
use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR};
use windmill_common::DYNAMIC_INPUT_CACHE;
@@ -1823,6 +1823,7 @@ pub struct ListQueueQuery {
pub concurrency_key: Option<String>,
pub allow_wildcards: Option<bool>,
pub trigger_kind: Option<JobTriggerKind>,
pub trigger_path: Option<String>,
}
impl From<ListCompletedQuery> for ListQueueQuery {
@@ -1855,6 +1856,7 @@ impl From<ListCompletedQuery> for ListQueueQuery {
concurrency_key: lcq.concurrency_key,
allow_wildcards: lcq.allow_wildcards,
trigger_kind: lcq.trigger_kind,
trigger_path: lcq.trigger_path,
}
}
}
@@ -1982,6 +1984,10 @@ pub fn filter_list_queue_query(
sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk)));
}
if let Some(tp) = &lq.trigger_path {
sqlb.and_where_eq("trigger", "?".bind(tp));
}
sqlb
}
@@ -2435,7 +2441,7 @@ async fn list_jobs(
}
sqlc.unwrap().limit(per_page).offset(offset).query()?
};
// tracing::info!("sql: {}", sql);
// tracing::info!("sql: {}", &sql);
let mut tx: Transaction<'_, Postgres> = user_db.begin(&authed).await?;
let jobs: Vec<UnifiedJob> = sqlx::query_as(&sql)
@@ -4053,7 +4059,7 @@ pub async fn run_flow(
flow_version_info: FlowVersionInfo,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<(Uuid, Option<String>)> {
let FlowVersionInfo {
version,
@@ -4159,7 +4165,7 @@ pub async fn run_flow_and_wait_result(
flow_version_info: FlowVersionInfo,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<Response> {
let (uuid, early_return) = run_flow(
authed,
@@ -4185,7 +4191,7 @@ pub async fn push_flow_job_by_path_into_queue(
flow_path: StripPath,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<(Uuid, Option<String>)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -4246,7 +4252,7 @@ pub async fn run_flow_by_version_inner(
version: i64,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<(Uuid, Option<String>)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -4426,7 +4432,7 @@ pub async fn push_script_job_by_path_into_queue(
script_path: StripPath,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<(Uuid, Option<bool>)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -6868,7 +6874,7 @@ pub async fn run_job_by_hash_inner(
script_hash: ScriptHash,
run_query: RunJobQuery,
args: PushArgsOwned,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> error::Result<(Uuid, Option<bool>)> {
#[cfg(feature = "enterprise")]
check_license_key_valid().await?;
@@ -7936,6 +7942,10 @@ pub fn filter_list_completed_query(
sqlb.and_where_eq("trigger_kind", "?".bind(&format!("{}", tk)));
}
if let Some(tp) = &lq.trigger_path {
sqlb.and_where_eq("trigger", "?".bind(tp));
}
sqlb
}

View File

@@ -0,0 +1,94 @@
use crate::{db::{ApiAuthed, DB}, triggers::INACTIVE_TRIGGER_SCHEDULED_FOR_DATE};
use axum::{
extract::{Extension, Path},
response::Json,
};
use windmill_common::{error, jobs::JobTriggerKind, utils::require_admin};
pub async fn resume_suspended_trigger_jobs(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path((w_id, trigger_kind, trigger_path)): Path<(String, JobTriggerKind, String)>,
) -> error::Result<Json<String>> {
require_admin(authed.is_admin, &authed.username)?;
let scheduled_for = INACTIVE_TRIGGER_SCHEDULED_FOR_DATE.clone();
let result = sqlx::query!(
r#"
UPDATE
v2_job_queue
SET
scheduled_for = now()
FROM
v2_job
WHERE
v2_job_queue.id = v2_job.id AND
v2_job_queue.running is FALSE AND
v2_job_queue.scheduled_for = $1 AND
v2_job_queue.workspace_id = $2 AND
v2_job.trigger_kind = $3 AND
v2_job.trigger = $4
"#,
scheduled_for,
w_id,
trigger_kind as _,
trigger_path
)
.execute(&db)
.await?;
let count = result.rows_affected();
let message = format!(
"Successfully resumed {} suspended job{} for trigger at path: {}",
count,
if count == 1 { "" } else { "s" },
&trigger_path
);
Ok(Json(message))
}
pub async fn cancel_suspended_trigger_jobs(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path((w_id, trigger_kind, trigger_path)): Path<(String, JobTriggerKind, String)>,
) -> error::Result<Json<String>> {
require_admin(authed.is_admin, &authed.username)?;
let scheduled_for = INACTIVE_TRIGGER_SCHEDULED_FOR_DATE.clone();
let result = sqlx::query!(
r#"
UPDATE
v2_job_queue
SET
canceled_by = $1,
canceled_reason = 'cancelled by trigger bulk operation',
scheduled_for = now()
FROM
v2_job
WHERE
v2_job_queue.id = v2_job.id AND
v2_job_queue.workspace_id = $2 AND
v2_job_queue.running is FALSE AND
v2_job_queue.scheduled_for = $3 AND
v2_job.trigger_kind = $4 AND
v2_job.trigger = $5
"#,
authed.username,
w_id,
scheduled_for,
trigger_kind as _,
trigger_path
)
.execute(&db)
.await?;
let count = result.rows_affected();
let message = format!(
"Successfully cancelled {} suspended job{} for trigger at path: {}",
count,
if count == 1 { "" } else { "s" },
&trigger_path
);
Ok(Json(message))
}

View File

@@ -739,6 +739,22 @@ pub fn generate_trigger_routers() -> Router {
);
}
{
use crate::triggers::global_handler::{
cancel_suspended_trigger_jobs, resume_suspended_trigger_jobs,
};
router = router
.route(
"/trigger/:trigger_kind/resume_suspended_trigger_job/*trigger_path",
post(resume_suspended_trigger_jobs),
)
.route(
"/trigger/:trigger_kind/cancel_suspended_trigger_job/*trigger_path",
post(cancel_suspended_trigger_jobs),
);
}
router
}

View File

@@ -41,7 +41,7 @@ use windmill_common::{
db::UserDB,
error::{Error, Result},
jobs::JobTriggerKind,
triggers::{TriggerInfo, TriggerKind},
triggers::{TriggerMetadata, TriggerKind},
utils::{not_found_if_none, require_admin, StripPath},
worker::CLOUD_HOSTED,
};
@@ -1040,7 +1040,7 @@ async fn route_job(
)
.map_err(|e| e.into_response())?;
let trigger_info = TriggerInfo::new(Some(trigger.path.clone()), JobTriggerKind::Http);
let trigger_info = TriggerMetadata::new(Some(trigger.path.clone()), JobTriggerKind::Http);
if !trigger.active_mode {
let _ = trigger_runnable(
&db,

View File

@@ -22,7 +22,7 @@ use tokio::sync::RwLock;
use windmill_common::{
error::{Error, Result},
jobs::JobTriggerKind,
triggers::{TriggerInfo, TriggerKind},
triggers::{TriggerMetadata, TriggerKind},
utils::report_critical_error,
DB, INSTANCE_NAME,
};
@@ -507,7 +507,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs {
format!("{}_trigger/{}", Self::TRIGGER_KIND, listening_trigger.path),
None,
listening_trigger.active_mode.unwrap_or(false),
TriggerInfo::new(Some(listening_trigger.path.clone()), Self::JOB_TRIGGER_KIND),
TriggerMetadata::new(Some(listening_trigger.path.clone()), Self::JOB_TRIGGER_KIND),
)
.await?;

View File

@@ -33,6 +33,7 @@ pub mod websocket;
mod handler;
mod listener;
pub mod trigger_helpers;
pub mod global_handler;
#[allow(unused)]
pub(crate) use handler::TriggerCrud;

View File

@@ -16,7 +16,7 @@ use windmill_common::{
jobs::{get_has_preprocessor_from_content_and_lang, script_path_to_payload, JobPayload},
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
triggers::{
HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerInfo, TriggerKind,
HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerMetadata, TriggerKind,
RUNNABLE_FORMAT_VERSION_CACHE,
},
users::username_to_permissioned_as,
@@ -519,7 +519,7 @@ pub async fn trigger_runnable_inner(
error_handler_args: Option<&sqlx::types::Json<HashMap<String, serde_json::Value>>>,
trigger_path: String,
job_id: Option<Uuid>,
trigger: TriggerInfo,
trigger: TriggerMetadata,
active_mode: Option<bool>,
) -> Result<(Uuid, Option<bool>, Option<String>)> {
let error_handler_args = error_handler_args.map(|args| {
@@ -588,7 +588,7 @@ pub async fn trigger_runnable(
trigger_path: String,
job_id: Option<Uuid>,
active_mode: bool,
trigger: TriggerInfo,
trigger: TriggerMetadata,
) -> Result<axum::response::Response> {
let uuid = trigger_runnable_inner(
db,
@@ -624,7 +624,7 @@ pub async fn trigger_runnable_and_wait_for_result(
error_handler_path: Option<&str>,
error_handler_args: Option<&sqlx::types::Json<HashMap<String, serde_json::Value>>>,
trigger_path: String,
trigger: TriggerInfo,
trigger: TriggerMetadata,
) -> Result<axum::response::Response> {
let username = authed.username.clone();
let (uuid, delete_after_use, early_return) = trigger_runnable_inner(
@@ -667,7 +667,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result(
error_handler_path: Option<&str>,
error_handler_args: Option<&sqlx::types::Json<HashMap<String, serde_json::Value>>>,
trigger_path: String,
trigger: TriggerInfo,
trigger: TriggerMetadata,
) -> Result<(Box<RawValue>, bool)> {
let username = authed.username.clone();
let (uuid, delete_after_use, early_return) = trigger_runnable_inner(
@@ -718,7 +718,7 @@ pub async fn trigger_runnable_and_wait_for_raw_result_with_error_ctx(
error_handler_path: Option<&str>,
error_handler_args: Option<&sqlx::types::Json<HashMap<String, serde_json::Value>>>,
trigger_path: String,
trigger: TriggerInfo,
trigger: TriggerMetadata,
) -> Result<Box<RawValue>> {
let (result, success) = trigger_runnable_and_wait_for_raw_result(
db,
@@ -759,7 +759,7 @@ async fn trigger_script_internal(
error_handler_args: Option<&sqlx::types::Json<HashMap<String, Box<RawValue>>>>,
trigger_path: String,
job_id: Option<Uuid>,
trigger: TriggerInfo,
trigger: TriggerMetadata,
scheduled_for: Option<DateTime<Utc>>,
) -> Result<(Uuid, Option<bool>)> {
if retry.is_none() && error_handler_path.is_none() {
@@ -808,7 +808,7 @@ async fn trigger_script_with_retry_and_error_handler(
error_handler_args: Option<&sqlx::types::Json<HashMap<String, Box<RawValue>>>>,
trigger_path: String,
job_id: Option<Uuid>,
trigger: TriggerInfo,
trigger: TriggerMetadata,
scheduled_for: Option<DateTime<Utc>>,
) -> Result<(Uuid, Option<bool>)> {
#[cfg(feature = "enterprise")]

View File

@@ -24,7 +24,7 @@ use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, Web
use windmill_common::{
error::{to_anyhow, Error, Result},
jobs::JobTriggerKind,
triggers::TriggerInfo,
triggers::TriggerMetadata,
utils::report_critical_error,
worker::to_raw_value,
DB,
@@ -95,7 +95,7 @@ impl ListeningTrigger<WebsocketConfig> {
None,
None,
"".to_string(), // doesn't matter as no retry/error handler
TriggerInfo::new(Some(self.path.to_owned()), JobTriggerKind::Websocket),
TriggerMetadata::new(Some(self.path.to_owned()), JobTriggerKind::Websocket),
)
.await
.map(|r| r.get().to_owned())?;
@@ -368,7 +368,7 @@ impl Listener for WebsocketTrigger {
None => (None, None, None),
};
let active_mode = active_mode.unwrap_or(false);
let trigger = TriggerInfo::new(Some(path.to_owned()), Self::JOB_TRIGGER_KIND);
let trigger = TriggerMetadata::new(Some(path.to_owned()), Self::JOB_TRIGGER_KIND);
if active_mode || extra.is_none() {
trigger_runnable(
db,

View File

@@ -12,7 +12,7 @@ use sqlx::{types::Json as SqlxJson, FromRow};
use windmill_common::{
error::{Error, Result},
jobs::JobTriggerKind,
triggers::{TriggerInfo, TriggerKind},
triggers::{TriggerMetadata, TriggerKind},
worker::to_raw_value,
DB,
};
@@ -114,7 +114,7 @@ pub async fn get_url_from_runnable_value(
None,
None,
"".to_string(), // doesn't matter as no retry/error handler
TriggerInfo::new(Some(path.to_owned()), JobTriggerKind::Websocket),
TriggerMetadata::new(Some(path.to_owned()), JobTriggerKind::Websocket),
)
.await?;

View File

@@ -85,13 +85,13 @@ lazy_static! {
Cache::new(1000);
}
pub struct TriggerInfo {
pub struct TriggerMetadata {
pub trigger_path: Option<String>,
pub trigger_kind: JobTriggerKind,
}
impl TriggerInfo {
pub fn new(trigger_path: Option<String>, trigger_kind: JobTriggerKind) -> TriggerInfo {
TriggerInfo { trigger_path, trigger_kind }
impl TriggerMetadata {
pub fn new(trigger_path: Option<String>, trigger_kind: JobTriggerKind) -> TriggerMetadata {
TriggerMetadata { trigger_path, trigger_kind }
}
}

View File

@@ -37,7 +37,7 @@ use windmill_common::auth::JobPerms;
use windmill_common::bench::BenchmarkIter;
use windmill_common::lockfiles::is_generated_from_raw_requirements;
use windmill_common::jobs::{JobTriggerKind, EMAIL_ERROR_HANDLER_USER_EMAIL};
use windmill_common::triggers::TriggerInfo;
use windmill_common::triggers::TriggerMetadata;
use windmill_common::utils::{configure_client, now_from_db};
use windmill_common::worker::{Connection, MIN_VERSION_SUPPORTS_DEBOUNCING, SCRIPT_TOKEN_EXPIRY};
@@ -3887,7 +3887,7 @@ pub async fn push<'c, 'd>(
// If we know there is already a debounce job, we can use this for debouncing.
// NOTE: Only works with dependency jobs triggered by relative imports
debounce_job_id_o: Option<Uuid>,
trigger: Option<TriggerInfo>,
trigger: Option<TriggerMetadata>,
) -> Result<(Uuid, Transaction<'c, Postgres>), Error> {
#[cfg(feature = "cloud")]
if *CLOUD_HOSTED {

View File

@@ -24,7 +24,7 @@ use windmill_common::jobs::JobPayload;
use windmill_common::jobs::JobTriggerKind;
use windmill_common::schedule::schedule_to_user;
use windmill_common::scripts::ScriptHash;
use windmill_common::triggers::TriggerInfo;
use windmill_common::triggers::TriggerMetadata;
use windmill_common::utils::WarnAfterExt;
use windmill_common::worker::to_raw_value;
use windmill_common::FlowVersionInfo;
@@ -502,7 +502,7 @@ pub async fn push_scheduled_job<'c>(
false,
None,
None,
Some(TriggerInfo::new(
Some(TriggerMetadata::new(
Some(schedule.path.clone()),
JobTriggerKind::Schedule,
)),

View File

@@ -6,9 +6,10 @@
import { workspaceStore } from '$lib/stores'
import RunRow from '../runs/RunRow.svelte'
import '../runs/runs-grid.css'
import { JobService } from '$lib/gen'
import { JobService, TriggerService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import { type JobTriggerType } from './utils'
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
type Props = {
active_mode: boolean
triggerPath: string
@@ -32,15 +33,23 @@
let loading = $state(false)
let error = $state<string | null>(null)
let processingAction = $state(false)
let isPreviousLoading = $state(false)
let isNextLoading = $state(false)
let workspace = $workspaceStore!
let containerWidth = $state(1000)
let currentPage = $state(1)
let perPage = $state(20)
let hasMorePages = $derived(queuedJobs.length === perPage)
$effect(() => {
if (shouldShowModal) {
fetchQueuedJobs()
}
})
async function fetchQueuedJobs() {
async function fetchQueuedJobs(resetPage = false) {
if (resetPage) {
currentPage = 1
}
loading = true
error = null
@@ -50,7 +59,8 @@
triggerKind: jobTriggerKind,
triggerPath,
running: false,
perPage: 100
perPage,
page: currentPage
})
queuedJobs = allSuspendedJobs
@@ -62,6 +72,26 @@
}
}
function nextPage() {
if (hasMorePages && !loading) {
isNextLoading = true
currentPage += 1
fetchQueuedJobs().finally(() => {
isNextLoading = false
})
}
}
function prevPage() {
if (currentPage > 1 && !loading) {
isPreviousLoading = true
currentPage -= 1
fetchQueuedJobs().finally(() => {
isPreviousLoading = false
})
}
}
async function runAllJobs() {
if (queuedJobs.length === 0) return
@@ -69,12 +99,10 @@
error = null
try {
const resumedJobs = await JobService.resumeSuspendedJobs({
const resumedJobs = await TriggerService.resumeSuspendedTriggerJobs({
workspace,
requestBody: {
trigger_kind: jobTriggerKind,
trigger_path: triggerPath
}
triggerKind: jobTriggerKind,
triggerPath
})
sendUserToast(resumedJobs)
} catch (e) {
@@ -82,7 +110,6 @@
console.error('Failed to run jobs:', e)
} finally {
processingAction = false
closeModal()
}
}
@@ -94,12 +121,10 @@
error = null
try {
await JobService.cancelSuspendedJobs({
await TriggerService.cancelSuspendedTriggerJobs({
workspace,
requestBody: {
trigger_kind: jobTriggerKind,
trigger_path: triggerPath
}
triggerKind: jobTriggerKind,
triggerPath
})
sendUserToast(`Successfully canceled all jobs`)
@@ -123,7 +148,9 @@
{#if shouldShowModal}
<Modal2
bind:isOpen={shouldShowModal}
title="{queuedJobs.length} job{queuedJobs.length === 1 ? '' : 's'} queued for this trigger"
title="{hasMorePages
? `${queuedJobs.length}+ suspended`
: `${queuedJobs.length} suspended`} job{queuedJobs.length === 1 ? '' : 's'} for this trigger"
target="#content"
fixedSize="lg"
>
@@ -142,16 +169,18 @@
{:else if queuedJobs.length === 0}
<div class="flex flex-col items-center w-full py-12 px-4">
<div class="text-center">
<div class="text-base font-medium text-secondary mb-2">No queued jobs found</div>
<div class="text-base font-medium text-secondary mb-2">No suspended jobs found</div>
<div class="text-sm text-tertiary"
>This trigger has no jobs waiting to be processed.</div
>This trigger has no suspended jobs waiting to be processed.</div
>
</div>
</div>
{:else}
<div class="flex-1 overflow-auto">
<div class="mb-3">
<h3 class="text-sm font-medium">Queued Jobs ({queuedJobs.length})</h3>
<h3 class="text-sm font-medium">
Suspended Jobs {#if hasMorePages}(Page {currentPage}){:else}({queuedJobs.length}){/if}
</h3>
<p class="text-xs text-gray-500 mt-1">Click on any job to view details</p>
</div>
@@ -185,10 +214,51 @@
</div>
</div>
{#if queuedJobs.length > 0 && (currentPage > 1 || hasMorePages)}
<div
class="w-full bg-surface border-t flex flex-row justify-between p-2 items-center gap-2"
>
<div class="flex flex-row gap-2 items-center">
<span class="text-xs text-secondary">
{queuedJobs.length}
{hasMorePages ? '+' : ''} suspended job{queuedJobs.length === 1 ? '' : 's'}
</span>
</div>
<div class="flex flex-row gap-3 items-center">
<div class="flex text-xs text-secondary">Page {currentPage}</div>
<Button
variant="subtle"
size="xs2"
startIcon={{ icon: ChevronLeft }}
on:click={prevPage}
disabled={currentPage === 1 || loading}
loading={isPreviousLoading}
>
Previous
</Button>
<Button
variant="subtle"
size="xs2"
endIcon={{ icon: ChevronRight }}
on:click={nextPage}
disabled={!hasMorePages || loading}
loading={isNextLoading}
>
Next
</Button>
</div>
</div>
{/if}
<div class="bg-blue-50 p-4 rounded-lg">
<p class="text-sm text-blue-700">
You are switching this trigger from inactive to active mode. What would you like to do
with the {queuedJobs.length} queued job{queuedJobs.length === 1 ? '' : 's'}?
with the {hasMorePages
? `${queuedJobs.length}+ suspended`
: `${queuedJobs.length} suspended`} job{queuedJobs.length === 1 ? '' : 's'}?
</p>
</div>
{/if}
@@ -198,7 +268,7 @@
<Button
variant="border"
size="sm"
on:click={discardAllJobs}
onClick={discardAllJobs}
disabled={processingAction}
color="red"
>
@@ -208,7 +278,7 @@
<Button
variant="contained"
size="sm"
on:click={runAllJobs}
onClick={runAllJobs}
disabled={processingAction}
color="green"
>