UI and http triggers

This commit is contained in:
HugoCasa
2025-11-28 16:49:46 +01:00
parent b95a8586b9
commit b7d4c63456
27 changed files with 974 additions and 551 deletions
@@ -4,6 +4,7 @@
import { twMerge } from 'tailwind-merge'
import type { MenubarMenuElements } from '@melt-ui/svelte'
import type { Item } from '$lib/utils'
import Tooltip from './Tooltip.svelte'
interface Props {
aiId?: string
@@ -52,6 +53,9 @@
<p title={item.displayName} class="truncate grow min-w-0 whitespace-nowrap text-left">
{item.displayName}
</p>
{#if item.tooltip}
<Tooltip>{item.tooltip}</Tooltip>
{/if}
{@render item.extra?.()}
</MenuItem>
{/each}
+5 -5
View File
@@ -12,7 +12,8 @@
ScriptService,
FlowService,
type ExtendedJobs,
OpenAPI
OpenAPI,
type JobTriggerKind
} from '$lib/gen'
import { sendUserToast } from '$lib/toast'
@@ -54,7 +55,6 @@
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import Select from '$lib/components/select/Select.svelte'
import AnimatedPane from '$lib/components/splitPanes/AnimatedPane.svelte'
import type { JobTriggerType } from '$lib/components/triggers/utils'
let { perPage = $bindable() }: { perPage: number } = $props()
@@ -115,8 +115,8 @@
? JSON.parse(decodeURIComponent(page.url.searchParams.get('result') ?? '{}'))
: undefined
)
let jobTriggerKind: JobTriggerType | undefined = $state(
(page.url.searchParams.get('job_trigger_kind') as JobTriggerType) ?? undefined
let jobTriggerKind: JobTriggerKind | undefined = $state(
(page.url.searchParams.get('job_trigger_kind') as JobTriggerKind) ?? undefined
)
// Handled on the main page
@@ -167,7 +167,7 @@
resultFilter = page.url.searchParams.get('result')
? JSON.parse(decodeURIComponent(page.url.searchParams.get('result') ?? '{}'))
: undefined
jobTriggerKind = (page.url.searchParams.get('job_trigger_kind') as JobTriggerType) ?? undefined
jobTriggerKind = (page.url.searchParams.get('job_trigger_kind') as JobTriggerKind) ?? undefined
// Handled on the main page
minTs = page.url.searchParams.get('min_ts') ?? undefined
@@ -50,7 +50,7 @@
{#if isOpen}
<Portal name="always-mounted" {target}>
<div
class={'fixed top-0 bottom-0 left-0 right-0 transition-all overflow-auto z-[9999] bg-black bg-opacity-60 w-full h-full'}
class={'fixed top-0 bottom-0 left-0 right-0 transition-all z-[1102] overflow-auto bg-black bg-opacity-60 w-full h-full'}
transition:fadeFast|local
>
<div class="flex min-h-full items-center justify-center p-8">
@@ -59,7 +59,7 @@
css?.popup?.style || ''
}`}
class={twMerge(
'max-h-screen-80 max-w-screen-80 rounded-lg relative bg-surface pt-2 px-4 pb-4',
'max-h-screen-80 max-w-screen-80 rounded-lg relative bg-surface p-4',
css?.popup?.class,
'wm-modal-form-popup'
)}
@@ -8,7 +8,8 @@
ConcurrencyGroupsService,
type ObscuredJob,
CancelablePromise,
CancelError
CancelError,
type JobTriggerKind
} from '$lib/gen'
import { sendUserToast } from '$lib/toast'
@@ -16,7 +17,6 @@
import { tweened, type Tweened } from 'svelte/motion'
import { subtractDaysFromDateString } from '$lib/utils'
import type { JobTriggerType } from '../triggers/utils'
import { CancelablePromiseUtils } from '$lib/cancelable-promise-utils'
interface Props {
@@ -31,7 +31,7 @@
showFutureJobs?: boolean
argFilter: string | undefined
resultFilter?: string | undefined
jobTriggerKind?: JobTriggerType | undefined
jobTriggerKind?: JobTriggerKind | undefined
schedulePath?: string | undefined
jobKindsCat?: string | undefined
minTs?: string | undefined
@@ -17,7 +17,8 @@
import DropdownSelect from '../DropdownSelect.svelte'
import TooltipV2 from '$lib/components/meltComponents/Tooltip.svelte'
import TextInput from '../text_input/TextInput.svelte'
import { jobTriggerTypes, triggerDisplayNamesMap, type JobTriggerType } from '../triggers/utils'
import { jobTriggerKinds, triggerDisplayNamesMap } from '../triggers/utils'
import type { JobTriggerKind } from '$lib/gen'
interface Props {
// Filters
@@ -31,7 +32,7 @@
argFilter: string
argError: string
resultFilter: string
jobTriggerKind: JobTriggerType | undefined
jobTriggerKind: JobTriggerKind | undefined
resultError: string
jobKindsCat: string
user?: string | null
@@ -964,7 +965,7 @@
{`Filter by what kind of trigger started the run.`}
</span>
<Select
items={jobTriggerTypes.map((value) => ({
items={jobTriggerKinds.map((value) => ({
label: triggerDisplayNamesMap[value],
value
}))}
@@ -1,146 +0,0 @@
<script lang="ts">
import Modal2 from '../common/modal/Modal2.svelte'
import Toggle from '../Toggle.svelte'
import type { QueuedJob } from '$lib/gen/types.gen'
import Button from '../common/button/Button.svelte'
import { workspaceStore } from '$lib/stores'
import RunRow from '../runs/RunRow.svelte'
import '../runs/runs-grid.css'
import { JobService, TriggerService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import { type JobTriggerType } from './utils'
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
type Props = {
suspended_mode: boolean
triggerPath: string
jobTriggerKind: JobTriggerType
}
let { suspended_mode = $bindable(), jobTriggerKind, triggerPath }: Props = $props()
let wasInInactiveMode = $state(!suspended_mode)
let shouldShowModal = $state(false)
$effect(() => {
if (suspended_mode && wasInInactiveMode) {
shouldShowModal = true
} else if (!suspended_mode) {
wasInInactiveMode = true
shouldShowModal = false
}
})
let queuedJobs = $state<QueuedJob[]>([])
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(resetPage = false) {
if (resetPage) {
currentPage = 1
}
loading = true
error = null
try {
const allSuspendedJobs = await JobService.listQueue({
workspace,
triggerKind: jobTriggerKind,
triggerPath,
running: false,
perPage,
page: currentPage
})
queuedJobs = allSuspendedJobs
} catch (e) {
error = `Failed to fetch queued jobs: ${e}`
console.error('Failed to fetch queued jobs:', e)
} finally {
loading = false
}
}
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
processingAction = true
error = null
try {
const resumedJobs = await TriggerService.resumeSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath
})
sendUserToast(resumedJobs)
} catch (e) {
error = `Failed to run jobs: ${e}`
console.error('Failed to run jobs:', e)
} finally {
processingAction = false
closeModal()
}
}
async function discardAllJobs() {
if (queuedJobs.length === 0) return
processingAction = true
error = null
try {
await TriggerService.cancelSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath
})
sendUserToast(`Successfully canceled all jobs`)
} catch (e) {
error = `Failed to discard jobs: ${e}`
console.error('Failed to discard jobs:', e)
} finally {
processingAction = false
closeModal()
}
}
function closeModal() {
wasInInactiveMode = false
shouldShowModal = false
}
</script>
<Toggle bind:checked={suspended_mode} options={{ right: 'Active', left: 'Inactive' }} />
@@ -5,7 +5,10 @@
import Toggle from '$lib/components/Toggle.svelte'
import { Tooltip } from '../meltComponents'
import DeleteTriggerButton from './DeleteTriggerButton.svelte'
import type { Trigger } from './utils'
import { type Trigger } from './utils'
import DropdownV2 from '../DropdownV2.svelte'
import TriggerSuspendedJobsModal from './TriggerSuspendedJobsModal.svelte'
import type { JobTriggerKind } from '$lib/gen'
interface Props {
saveDisabled: any
@@ -15,10 +18,15 @@
isLoading: any
permissions: 'write' | 'create' | 'none'
isDeployed: boolean
kind: JobTriggerKind
path: string
editedAt: string | undefined
suspendedMode?: boolean
extra?: Snippet
onDelete?: () => void
onReset?: () => void
onToggleEnabled?: (enabled: boolean) => void
onToggleSuspendedMode?: (suspendedMode: boolean, enabled?: boolean) => void
onUpdate?: () => void
cloudDisabled?: boolean
trigger?: Trigger
@@ -32,30 +40,72 @@
isLoading,
permissions,
isDeployed,
kind,
path,
suspendedMode = false,
editedAt,
extra,
onDelete,
onReset,
onToggleEnabled,
onUpdate,
onToggleSuspendedMode,
cloudDisabled = false,
trigger
}: Props = $props()
const canSave = $derived((permissions === 'write' && edit) || permissions === 'create')
let showSuspendedJobsModal = $state(false)
</script>
{#if path && suspendedMode}
<TriggerSuspendedJobsModal
bind:shouldShowModal={showSuspendedJobsModal}
{suspendedMode}
triggerPath={path}
jobTriggerKind={kind}
{onToggleSuspendedMode}
{editedAt}
/>
{/if}
{#if !allowDraft}
{@render extra?.()}
{#if edit && enabled !== undefined}
<Toggle
size="sm"
disabled={permissions === 'none'}
checked={enabled}
options={{ right: 'enable', left: 'disable' }}
on:change={({ detail }) => {
onToggleEnabled?.(detail)
}}
/>
{#if suspendedMode}
<Button
on:click={() => {
showSuspendedJobsModal = true
}}
variant="accent"
>
See suspended jobs
</Button>
{:else}
<Toggle
size="sm"
disabled={permissions === 'none'}
checked={enabled}
options={{ right: 'enable', left: 'disable' }}
on:change={({ detail }) => {
onToggleEnabled?.(detail)
}}
/>
<DropdownV2
items={[
{
displayName: 'Suspend job executions',
action: () => {
onToggleSuspendedMode?.(true)
},
tooltip:
'Suspend job executions for this trigger, allowing you to individually resume or cancel them and reassign them to a different runnable'
}
]}
/>
{/if}
{/if}
{#if canSave}
<Button
@@ -1,44 +1,55 @@
<script lang="ts">
import Modal2 from '../common/modal/Modal2.svelte'
import type { QueuedJob } from '$lib/gen/types.gen'
import type { JobTriggerKind, QueuedJob } from '$lib/gen/types.gen'
import Button from '../common/button/Button.svelte'
import { workspaceStore } from '$lib/stores'
import RunRow from '../runs/RunRow.svelte'
import '../runs/runs-grid.css'
import { JobService, TriggerService } from '$lib/gen'
import { HttpTriggerService, JobService, TriggerService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import { type JobTriggerType } from './utils'
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
import Cell from '$lib/components/table/Cell.svelte'
import DataTable from '$lib/components/table/DataTable.svelte'
import Head from '$lib/components/table/Head.svelte'
import Row from '$lib/components/table/Row.svelte'
import { Skeleton } from '../common'
import { PlayCircle, Trash2 } from 'lucide-svelte'
import { displayDate } from '$lib/utils'
import Badge from '../common/badge/Badge.svelte'
import Tooltip from '../Tooltip.svelte'
type Props = {
suspended_mode: boolean
suspendedMode: boolean
triggerPath: string
jobTriggerKind: JobTriggerType
jobTriggerKind: JobTriggerKind
shouldShowModal: boolean
editedAt: string | undefined
onTriggerEnabled?: () => void
onToggleSuspendedMode?: (suspendedMode: boolean, enabled?: boolean) => void
}
let { suspended_mode = $bindable(), jobTriggerKind, triggerPath }: Props = $props()
let {
jobTriggerKind,
triggerPath,
shouldShowModal = $bindable(),
onTriggerEnabled,
editedAt
}: Props = $props()
let wasInInactiveMode = $state(!suspended_mode)
let shouldShowModal = $state(false)
$effect(() => {
if (suspended_mode && wasInInactiveMode) {
shouldShowModal = true
} else if (!suspended_mode) {
wasInInactiveMode = true
shouldShowModal = false
}
})
let queuedJobs = $state<QueuedJob[]>([])
let selectedJobs = $state<Set<string>>(new Set())
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)
let headerHeight = $state(0)
let contentHeight = $state(0)
// Derived states for checkbox logic
let allSelected = $derived(queuedJobs.length > 0 && selectedJobs.size === queuedJobs.length)
let someSelected = $derived(selectedJobs.size > 0 && selectedJobs.size < queuedJobs.length)
let hasSelectedJobs = $derived(selectedJobs.size > 0)
$effect(() => {
if (shouldShowModal) {
fetchQueuedJobs()
@@ -64,6 +75,7 @@
})
queuedJobs = allSuspendedJobs
selectedJobs = new Set()
} catch (e) {
error = `Failed to fetch queued jobs: ${e}`
console.error('Failed to fetch queued jobs:', e)
@@ -74,21 +86,81 @@
function nextPage() {
if (hasMorePages && !loading) {
isNextLoading = true
currentPage += 1
fetchQueuedJobs().finally(() => {
isNextLoading = false
})
fetchQueuedJobs()
}
}
function prevPage() {
if (currentPage > 1 && !loading) {
isPreviousLoading = true
currentPage -= 1
fetchQueuedJobs().finally(() => {
isPreviousLoading = false
fetchQueuedJobs()
}
}
function toggleSelectAll() {
if (allSelected) {
selectedJobs = new Set()
} else {
selectedJobs = new Set(queuedJobs.map((job) => job.id))
}
}
function toggleJobSelection(jobId: string) {
const newSelection = new Set(selectedJobs)
if (newSelection.has(jobId)) {
newSelection.delete(jobId)
} else {
newSelection.add(jobId)
}
selectedJobs = newSelection
}
async function runSelectedJobs() {
if (selectedJobs.size === 0) return
processingAction = true
error = null
try {
const jobIds = Array.from(selectedJobs)
await TriggerService.resumeSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath,
requestBody: { job_ids: jobIds }
})
sendUserToast(`Successfully resumed ${jobIds.length} job${jobIds.length > 1 ? 's' : ''}`)
await fetchQueuedJobs()
} catch (e) {
error = `Failed to run selected jobs: ${e}`
console.error('Failed to run selected jobs:', e)
} finally {
processingAction = false
}
}
async function discardSelectedJobs() {
if (selectedJobs.size === 0) return
processingAction = true
error = null
try {
const jobIds = Array.from(selectedJobs)
await TriggerService.cancelSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath,
requestBody: { job_ids: jobIds }
})
sendUserToast(`Successfully canceled ${jobIds.length} job${jobIds.length > 1 ? 's' : ''}`)
await fetchQueuedJobs()
} catch (e) {
error = `Failed to discard selected jobs: ${e}`
console.error('Failed to discard selected jobs:', e)
} finally {
processingAction = false
}
}
@@ -102,15 +174,23 @@
const resumedJobs = await TriggerService.resumeSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath
triggerPath,
requestBody: {}
})
//TODO: Add support for other trigger types
await HttpTriggerService.updateHttpTriggerStatus({
workspace,
path: triggerPath,
requestBody: { enabled: true, suspended_mode: false }
})
sendUserToast(resumedJobs)
closeModal()
} catch (e) {
error = `Failed to run jobs: ${e}`
console.error('Failed to run jobs:', e)
} finally {
processingAction = false
closeModal()
}
}
@@ -124,166 +204,185 @@
await TriggerService.cancelSuspendedTriggerJobs({
workspace,
triggerKind: jobTriggerKind,
triggerPath
triggerPath,
requestBody: {}
})
await HttpTriggerService.updateHttpTriggerStatus({
workspace,
path: triggerPath,
requestBody: { enabled: false, suspended_mode: false }
})
sendUserToast(`Successfully canceled all jobs`)
closeModal()
} catch (e) {
error = `Failed to discard jobs: ${e}`
console.error('Failed to discard jobs:', e)
} finally {
processingAction = false
closeModal()
}
}
function enableTrigger() {
onTriggerEnabled?.()
closeModal()
}
function closeModal() {
wasInInactiveMode = false
shouldShowModal = false
}
</script>
{#if shouldShowModal}
<Modal2
bind:isOpen={shouldShowModal}
title="{hasMorePages
? `${queuedJobs.length}+ suspended`
: `${queuedJobs.length} suspended`} job{queuedJobs.length === 1 ? '' : 's'} for this trigger"
target="#content"
fixedSize="lg"
>
<div class="flex w-full flex-col gap-4 h-full">
{#if loading}
<div class="flex items-center justify-center py-8">
<div
class="animate-spin h-6 w-6 border-2 border-blue-500 border-t-transparent rounded-full"
></div>
<span class="ml-2">Loading queued jobs...</span>
</div>
{:else if error}
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{error}
</div>
{: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 suspended jobs found</div>
<div class="text-sm text-tertiary"
>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">
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>
<div class="divide-y h-full border min-w-[650px]" bind:clientWidth={containerWidth}>
<div
class="bg-surface-secondary sticky top-0 w-full py-2 pr-4 grid grid-runs-table-no-tag"
>
<div class="text-2xs px-2 font-semibold">Status</div>
<div class="text-xs font-semibold">Started</div>
<div class="text-xs font-semibold">Duration</div>
<div class="text-xs font-semibold">Path</div>
<div class="text-xs font-semibold">Triggered by</div>
<div class=""></div>
</div>
<div class="h-full">
{#each queuedJobs as job}
<div class="flex flex-row items-center h-[42px] w-full">
<RunRow
{job}
{containerWidth}
showTag={false}
activeLabel={null}
on:select={() => {
window.open(`/run/${job.id}?workspace=${workspace}`, '_blank')
}}
/>
</div>
{/each}
</div>
</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 {hasMorePages
? `${queuedJobs.length}+ suspended`
: `${queuedJobs.length} suspended`} job{queuedJobs.length === 1 ? '' : 's'}?
</p>
</div>
{/if}
<div class="flex gap-2 pt-4 border-t">
{#if !loading && !error && queuedJobs.length > 0}
<Button
variant="border"
size="sm"
onClick={discardAllJobs}
disabled={processingAction}
color="red"
>
{processingAction ? 'Discarding...' : 'Discard All Jobs'}
</Button>
<Button
variant="contained"
size="sm"
onClick={runAllJobs}
disabled={processingAction}
color="green"
>
{processingAction ? 'Running...' : 'Run All Jobs'}
</Button>
{/if}
<Modal2
zIndex={1102}
bind:isOpen={shouldShowModal}
title="{hasMorePages
? `${queuedJobs.length}+ suspended`
: `${queuedJobs.length} suspended`} job{queuedJobs.length === 1 ? '' : 's'} for this trigger"
target="#content"
fixedSize="lg"
>
<div class="flex w-full flex-col gap-4 h-full">
{#if error}
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{error}
</div>
{/if}
<div class="relative grow min-h-0 w-full">
<DataTable
size="xs"
paginated
on:next={nextPage}
on:previous={prevPage}
bind:currentPage
hasMore={hasMorePages}
bind:contentHeight
>
<Head>
<tr bind:clientHeight={headerHeight}>
<Cell head first class="w-12">
<div class="h-4 w-4">
<input
type="checkbox"
checked={allSelected}
indeterminate={someSelected}
onchange={toggleSelectAll}
disabled={loading || queuedJobs.length === 0}
class="cursor-pointer w-auto"
/>
</div>
</Cell>
<Cell head class="min-w-24">Created At</Cell>
<Cell head class="min-w-32 ">Script/Flow Path</Cell>
</tr>
</Head>
{#if loading}
<tbody>
{#each new Array(3) as _}
<Row>
{#each new Array(4) as _}
<Cell>
<Skeleton layout={[[5]]} />
</Cell>
{/each}
</Row>
{/each}
</tbody>
{:else if queuedJobs.length === 0}
<div class="absolute top-0 left-0 w-full h-full center-center">
<p class="text-center text-gray-500 mt-4">No suspended jobs found.</p>
</div>
{:else}
<tbody class="divide-y border-b w-full overflow-y-auto">
{#each queuedJobs as job}
<Row>
<Cell class="w-12 sm:pl-3">
<div class="h-4 w-4">
<input
type="checkbox"
checked={selectedJobs.has(job.id)}
onchange={() => toggleJobSelection(job.id)}
disabled={processingAction}
class="cursor-pointer"
/>
</div>
</Cell>
<Cell wrap>{displayDate(job.created_at)}</Cell>
<Cell wrap class="flex flex-row gap-2">
<a
href="/run/{job.id}?workspace={workspace}"
target="_blank"
class="text-blue-600 hover:underline"
>
<div class="flex-shrink min-w-0 break-words">{job.script_path || '-'}</div>
</a>
{#if editedAt && job.created_at && new Date(editedAt) > new Date(job.created_at)}
<Badge color="yellow"
>Outdated <Tooltip>
Trigger was edited after these jobs were created and suspended. They will be
reassigned to match the new trigger configuration, in particular the
runnable, retry and error handling settings.
</Tooltip>
</Badge>
{/if}
</Cell>
</Row>
{/each}
</tbody>
{/if}
</DataTable>
</div>
</Modal2>
{/if}
<!-- Bottom right conditional buttons -->
<div class="flex justify-end gap-2">
{#if queuedJobs.length === 0}
<!-- No jobs found - show Enable Trigger -->
<Button size="sm" disabled={processingAction} on:click={enableTrigger}>
Enable Trigger
</Button>
<Button size="sm" disabled={processingAction} on:click={enableTrigger}>
Enable Trigger
</Button>
{:else if hasSelectedJobs}
<!-- Jobs selected - show Discard Selected and Run Selected -->
<Button
startIcon={{ icon: Trash2 }}
size="sm"
disabled={processingAction}
on:click={discardSelectedJobs}
>
Discard Selected ({selectedJobs.size})
</Button>
<Button
startIcon={{ icon: PlayCircle }}
size="sm"
disabled={processingAction}
on:click={runSelectedJobs}
>
Run selected ({selectedJobs.size})
</Button>
{:else}
<Button
startIcon={{ icon: Trash2 }}
size="sm"
disabled={processingAction}
on:click={discardAllJobs}
>
Discard all jobs and disable
</Button>
<Button
startIcon={{ icon: PlayCircle }}
size="sm"
disabled={processingAction}
on:click={runAllJobs}
>
Run all jobs and resume
</Button>
{/if}
</div>
</div>
</Modal2>
@@ -25,7 +25,6 @@
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import { saveEmailTriggerFromCfg } from './utils'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
let {
useDrawer = true,
@@ -316,8 +315,6 @@
</Section>
{/if}
<TriggerActiveMode triggerPath={path} jobTriggerKind={'email'} bind:suspended_mode />
<EmailTriggerEditorConfigSection
initialTriggerPath={initialPath}
bind:local_part
@@ -29,7 +29,6 @@
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import Subsection from '$lib/components/Subsection.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
let drawer: Drawer | undefined = $state(undefined)
let initialPath = $state('')
@@ -395,8 +394,6 @@
{/if}
</div>
</Section>
<TriggerActiveMode triggerPath={path} jobTriggerKind={'gcp'} bind:suspended_mode />
{/if}
<GcpTriggerEditorConfigSection
@@ -45,7 +45,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
let {
useDrawer = true,
@@ -113,6 +112,7 @@
let optionTabSelected: 'request_options' | 'error_handler' | 'retries' = $state('request_options')
let errorHandlerSelected: ErrorHandler = $state('slack')
let suspended_mode = $state(true)
let editedAt: string | undefined = $state(undefined)
const isAdmin = $derived($userStore?.is_admin || $userStore?.is_super_admin)
const routeConfig = $derived.by(getRouteConfig)
const captureConfig = $derived.by(isEditor ? getCaptureConfig : () => ({}))
@@ -253,6 +253,7 @@
error_handler_args = defaultValues?.error_handler_args ?? {}
retry = defaultValues?.retry ?? undefined
errorHandlerSelected = getHandlerType(error_handler_path ?? '')
editedAt = undefined
} finally {
clearTimeout(loader)
drawerLoading = false
@@ -293,7 +294,8 @@
error_handler_args = cfg?.error_handler_args ?? {}
retry = cfg?.retry
errorHandlerSelected = getHandlerType(error_handler_path ?? '')
suspended_mode = cfg?.suspended_mode ?? true
suspended_mode = cfg?.suspended_mode ?? false
editedAt = cfg?.edited_at ?? undefined
}
async function loadTrigger(defaultConfig?: Partial<HttpTrigger>): Promise<void> {
@@ -377,7 +379,22 @@
workspace: $workspaceStore ?? '',
requestBody: { enabled: newEnabled }
})
sendUserToast(`${newEnabled ? 'enabled' : 'disabled'} HTTP trigger ${initialPath}`)
sendUserToast(`${newEnabled ? 'Enabled' : 'Disabled'} HTTP trigger ${initialPath}`)
}
}
async function handleToggleSuspendedMode(newSuspendedMode: boolean, newEnabled: boolean = true) {
suspended_mode = newSuspendedMode
enabled = newSuspendedMode || newEnabled
if (!trigger?.draftConfig) {
await HttpTriggerService.updateHttpTriggerStatus({
workspace: $workspaceStore ?? '',
path: initialPath,
requestBody: { suspended_mode: newSuspendedMode, enabled: newSuspendedMode || newEnabled }
})
sendUserToast(
`${newSuspendedMode ? 'Suspended' : newEnabled ? 'Resumed' : 'Disabled'} HTTP trigger ${initialPath}`
)
}
}
@@ -610,8 +627,6 @@
{/if}
</div>
</Section>
<TriggerActiveMode triggerPath={path} jobTriggerKind={'http'} bind:suspended_mode />
{/if}
<RouteEditorConfigSection
@@ -853,6 +868,7 @@
{saveDisabled}
{enabled}
onToggleEnabled={handleToggleEnabled}
onToggleSuspendedMode={handleToggleSuspendedMode}
{allowDraft}
{edit}
isLoading={deploymentLoading}
@@ -860,6 +876,10 @@
{onReset}
{onDelete}
{isDeployed}
kind={'http'}
{path}
suspendedMode={suspended_mode}
{editedAt}
/>
{/if}
{/snippet}
@@ -19,7 +19,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
interface Props {
useDrawer?: boolean
@@ -386,8 +385,6 @@
{/if}
</div>
</Section>
<TriggerActiveMode triggerPath={path} jobTriggerKind={'kafka'} bind:suspended_mode />
{/if}
<KafkaTriggersConfigSection
@@ -27,7 +27,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
@@ -421,8 +420,6 @@
</Section>
{/if}
<TriggerActiveMode triggerPath={path} jobTriggerKind={'mqtt'} bind:suspended_mode />
<MqttEditorConfigSection
bind:mqtt_resource_path
bind:subscribe_topics
@@ -19,7 +19,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
interface Props {
useDrawer?: boolean
@@ -402,8 +401,6 @@
</Section>
{/if}
<TriggerActiveMode triggerPath={path} jobTriggerKind={'nats'} bind:suspended_mode />
<NatsTriggersConfigSection
{path}
bind:natsResourcePath
@@ -35,7 +35,6 @@
import TestingBadge from '../testingBadge.svelte'
import { getHandlerType, handleConfigChange, type Trigger } from '../utils'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
import { fade } from 'svelte/transition'
import MultiSelect from '$lib/components/select/MultiSelect.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
@@ -580,7 +579,6 @@
</Section>
{/if}
<TriggerActiveMode triggerPath={path} jobTriggerKind={'postgres'} bind:suspended_mode />
<Section label="Database">
{#snippet badge()}
{#if isEditor}
@@ -24,7 +24,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
interface Props {
useDrawer?: boolean
@@ -387,8 +386,6 @@
{/if}
</div>
</Section>
<TriggerActiveMode triggerPath={path} jobTriggerKind={'sqs'} bind:suspended_mode />
{/if}
<SqsTriggerEditorConfigSection
@@ -8,6 +8,7 @@ import type {
CaptureTriggerKind,
ErrorHandler,
Flow,
JobTriggerKind,
NewScript,
TriggersCount
} from '$lib/gen/types.gen'
@@ -54,8 +55,9 @@ export type TriggerType =
| 'poll'
| 'cli'
export const jobTriggerTypes = [
export const jobTriggerKinds: JobTriggerKind[] = [
'webhook',
'default_email',
'http',
'websocket',
'kafka',
@@ -66,9 +68,7 @@ export const jobTriggerTypes = [
'postgres',
'schedule',
'gcp'
] as const
export type JobTriggerType = (typeof jobTriggerTypes)[number]
]
export type Trigger = {
type: TriggerType
@@ -34,7 +34,6 @@
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerActiveMode from '../TriggerActiveMode.svelte'
interface Props {
useDrawer?: boolean
@@ -494,8 +493,6 @@
/>
</Section>
<TriggerActiveMode triggerPath={path} jobTriggerKind={'websocket'} bind:suspended_mode />
<WebsocketEditorConfigSection
bind:url
bind:url_runnable_args
+1
View File
@@ -1344,6 +1344,7 @@ export type Item = {
hide?: boolean | undefined
extra?: Snippet
id?: string
tooltip?: string
}
export function isObjectTooBig(obj: any): boolean {