mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
5d79f33590
* Remove $$props.field usage * Rename slots to ensure no hyphen * _props * _trigger * OnSelectedIteration type correct capitalization * rename _content * Remove afterUpdate * Migrate everything to svelte 5 * array bind * Fix popover * type never * nit fixes * Fixed many trivial errors * onClick * Fix errors * use let: * nit typing * fix: wrap state_referenced_locally vars with untrack() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add untrack import * Fix all syntax errors due to untrack migration * Fix undefined errors * Fix more undefined errors * untrack(() => initialOpen) * svelte-ignore * Fix state_descriptors_fixed error in Chart.svelte Use $state.snapshot() to pass plain copies of data/options to Chart.js instead of $state proxies. Chart.js's listenArrayEvents tries to define property descriptors on data arrays, which Svelte 5 proxies reject. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit typing * Merge issue * Fix "path is not set" error in resource picker / editor * Fix InputTransformForm error when rerunning some flows * fix npm run check --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
190 lines
4.8 KiB
Svelte
190 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import { JobService, type Script } from '$lib/gen'
|
|
import { Badge, Button, Drawer } from './common'
|
|
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
|
import { workspaceStore } from '$lib/stores'
|
|
import { base } from '$lib/base'
|
|
import { displayDate, sleep, sendUserToast } from '$lib/utils'
|
|
import TableCustom from './TableCustom.svelte'
|
|
import { Hourglass, Loader2, Play, RefreshCw } from 'lucide-svelte'
|
|
|
|
let dispatch = createEventDispatcher()
|
|
let drawer: Drawer | undefined = $state()
|
|
|
|
let script: Script | undefined = $state()
|
|
let loadQueuedJobs = $state(true)
|
|
let queuedJobsLoading = $state(false)
|
|
let queuedJobs: {
|
|
status: 'running' | 'queued'
|
|
jobId: string
|
|
scheduledFor: string
|
|
scriptHash: string
|
|
}[] = $state([])
|
|
|
|
let cancellingInProgress = $state(false)
|
|
|
|
async function continuouslyLoadQueuedJobs() {
|
|
while (loadQueuedJobs) {
|
|
loadQueuedJobsOnce()
|
|
await sleep(3 * 1000)
|
|
}
|
|
}
|
|
|
|
async function loadQueuedJobsOnce() {
|
|
if (queuedJobsLoading) {
|
|
return
|
|
}
|
|
const timeStart = new Date().getTime()
|
|
queuedJobsLoading = true
|
|
let qjs = await JobService.listQueue({
|
|
workspace: $workspaceStore ?? '',
|
|
orderDesc: false,
|
|
scriptPathExact: script?.path
|
|
})
|
|
let loadingQueuedJobs: {
|
|
status: 'running' | 'queued'
|
|
jobId: string
|
|
scheduledFor: string
|
|
scriptHash: string
|
|
}[] = []
|
|
for (const qj of qjs) {
|
|
loadingQueuedJobs.push({
|
|
status: qj.started_at ? 'running' : 'queued',
|
|
jobId: qj.id,
|
|
scriptHash: qj.script_hash ?? '',
|
|
scheduledFor: displayDate(qj.scheduled_for, true)
|
|
})
|
|
}
|
|
queuedJobs = loadingQueuedJobs
|
|
const endStart = new Date().getTime()
|
|
// toggle queuedJobsLoading to false in 1 secs to let some time for the animation to play
|
|
setTimeout(
|
|
() => {
|
|
queuedJobsLoading = false
|
|
},
|
|
3000 - (endStart - timeStart)
|
|
)
|
|
}
|
|
|
|
async function scaleToZero() {
|
|
cancellingInProgress = true
|
|
await JobService.cancelPersistentQueuedJobs({
|
|
workspace: $workspaceStore ?? '',
|
|
path: script?.path ?? '',
|
|
requestBody: {
|
|
reason: undefined
|
|
}
|
|
})
|
|
sendUserToast(`All jobs cancelled for ${script?.path}`)
|
|
cancellingInProgress = false
|
|
}
|
|
|
|
export async function open(persistentScript: Script | undefined) {
|
|
if (persistentScript === undefined) {
|
|
console.log('Unable to open persistent script drawer without a proper script definition')
|
|
return
|
|
}
|
|
script = persistentScript!
|
|
loadQueuedJobs = true
|
|
continuouslyLoadQueuedJobs()
|
|
drawer?.openDrawer?.()
|
|
}
|
|
|
|
async function exit() {
|
|
loadQueuedJobs = false
|
|
drawer?.closeDrawer?.()
|
|
}
|
|
|
|
onDestroy(() => {
|
|
loadQueuedJobs = false
|
|
})
|
|
</script>
|
|
|
|
<Drawer
|
|
bind:this={drawer}
|
|
on:close={() => {
|
|
loadQueuedJobs = false
|
|
dispatch('close')
|
|
}}
|
|
size="800px"
|
|
>
|
|
<DrawerContent
|
|
title="Persistent script"
|
|
overflow_y={false}
|
|
on:close={exit}
|
|
tooltip="Manage runs of persistent scripts. Scaling a persistent script to zero will cancel all current runs of this script based on the script path."
|
|
>
|
|
<div class="flex gap-2 items-center justify-between">
|
|
<h2>
|
|
Queued jobs for {script?.path}
|
|
</h2>
|
|
<Button size="md" btnClasses="w-full h-8" variant="default" on:click={loadQueuedJobsOnce}>
|
|
<RefreshCw class={queuedJobsLoading ? 'animate-spin' : ''} size={14} />
|
|
</Button>
|
|
</div>
|
|
<TableCustom>
|
|
{#snippet headerRow()}
|
|
<tr>
|
|
<th class="text-xs">Script Hash</th>
|
|
<th class="text-xs">Job ID</th>
|
|
<th class="text-xs">Status</th>
|
|
<th class="text-xs">Scheduled For</th>
|
|
</tr>
|
|
{/snippet}
|
|
{#snippet body()}
|
|
<tbody>
|
|
{#each queuedJobs as { jobId, status, scriptHash, scheduledFor }}
|
|
<tr class="">
|
|
<td class="text-xs">
|
|
<a
|
|
class="pr-3"
|
|
href="{base}/scripts/get/{scriptHash}?workspace={$workspaceStore}"
|
|
target="_blank"
|
|
>
|
|
{scriptHash}
|
|
</a>
|
|
</td>
|
|
<td class="text-xs">
|
|
<a
|
|
class="pr-3"
|
|
href="{base}/run/{jobId}?workspace={$workspaceStore}"
|
|
target="_blank">{jobId.substring(24)}</a
|
|
>
|
|
</td>
|
|
<td class="text-xs">
|
|
{#if status === 'running'}
|
|
<Badge color="yellow" baseClass="!px-1.5">
|
|
<Play size={14} />
|
|
</Badge>
|
|
{:else}
|
|
<Badge baseClass="!px-1.5">
|
|
<Hourglass size={14} />
|
|
</Badge>
|
|
{/if}
|
|
</td>
|
|
<td class="text-xs">{scheduledFor}</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
{/snippet}
|
|
</TableCustom>
|
|
|
|
{#snippet actions()}
|
|
<div class="flex gap-1">
|
|
<Button
|
|
color="red"
|
|
disabled={cancellingInProgress === true || queuedJobs.length === 0}
|
|
on:click={scaleToZero}
|
|
>
|
|
{#if cancellingInProgress}
|
|
<Loader2 class="animate-spin" /> Stopping jobs
|
|
{:else}
|
|
Scale down to 0
|
|
{/if}
|
|
</Button>
|
|
</div>
|
|
{/snippet}
|
|
</DrawerContent>
|
|
</Drawer>
|