Files
windmill/frontend/src/lib/components/QueuePosition.svelte
T
6186a0645d feat: edit variables, resources and triggers in their own session tab (#11206)
* feat: edit variables, resources and triggers in their own session tab

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: take a trigger's flow kind from its config and name the written trigger

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: one operating-workspace context for editors acting on a session's workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: reload page item tabs only in sessions acting on the tool's workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: resolve open in workspace for every preview tab kind in one place

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: guard every component under a session editor against the navigation workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: show a page item tab whose item is gone instead of mounting its editor

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: follow a saved page item in the tab's own workspace and restore in-frame rows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: close a page item tab from its editor header

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: judge trigger permissions by the user acting in the session workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: judge every session-editor permission by the user acting in that workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep session permissions reactive to the acting role and restore tabs as last seen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: default Path's owner controls to the user acting in its workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-09-19 23:29:14 +02:00

131 lines
3.8 KiB
Svelte

<script lang="ts">
import { JobService } from '$lib/gen'
import { forLater, getDbClockNow } from '$lib/forLater'
import { displayDate } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
jobId,
workspaceId,
minimal = false
}: { jobId: string; workspaceId?: string | undefined; minimal?: boolean } = $props()
let queuePositionInterval: number | undefined
let queueState = $state(undefined) as undefined | { position?: number }
let fetchingQueuePosition = false
let workspace = $derived(workspaceId ?? $operatingWorkspace)
let scheduledFor = $state(undefined) as undefined | number
// Bumped when a scheduled-for-later deadline passes so `isScheduledForLater`
// re-evaluates — `forLater` reads the clock but isn't otherwise reactive, so
// a job parked in the future would never flip to "queued" without this tick.
let clockTick = $state(0)
let deadlineTimeout: ReturnType<typeof setTimeout> | undefined
let isScheduledForLater = $derived.by(() => {
clockTick
return scheduledFor != undefined && forLater(scheduledFor)
})
let scheduledForTimeout: number | undefined
$effect(() => {
if (jobId && workspace) {
clearTimeout(scheduledForTimeout)
queueState = undefined
scheduledForTimeout = setTimeout(() => {
try {
JobService.getScheduledFor({
workspace: workspace,
id: jobId
})
.then((response) => {
scheduledFor = response
})
.catch((error) => {
console.error('Failed to fetch scheduled for:', error)
})
} catch (error) {
console.error('Failed to fetch scheduled for:', error)
}
}, 2000)
}
})
// Flip out of the "scheduled for later" state exactly when the deadline
// passes, so a job whose scheduled time arrives with no free worker then
// correctly surfaces its queue position.
$effect(() => {
clearTimeout(deadlineTimeout)
if (scheduledFor != undefined) {
const ms = new Date(scheduledFor).getTime() - 5000 - getDbClockNow().getTime()
if (ms > 0) {
deadlineTimeout = setTimeout(() => clockTick++, ms)
}
}
return () => clearTimeout(deadlineTimeout)
})
$effect(() => {
// Only poll the queue position for jobs actually waiting for a worker —
// a job scheduled for the future isn't "in line", so its position is
// meaningless (and the query counts every job scheduled before it).
if (scheduledFor && !isScheduledForLater) {
// Initial fetch
fetchQueuePosition()
// Set up interval to refresh every 2 seconds
queuePositionInterval = setInterval(() => {
fetchQueuePosition()
}, 5000)
} else {
// Clear interval when not loading
if (queuePositionInterval) {
clearInterval(queuePositionInterval)
queuePositionInterval = undefined
}
queueState = undefined
}
return () => {
scheduledForTimeout && clearTimeout(scheduledForTimeout)
if (queuePositionInterval) {
clearInterval(queuePositionInterval)
}
}
})
async function fetchQueuePosition() {
if (!workspace || !scheduledFor || fetchingQueuePosition) return
try {
fetchingQueuePosition = true
queueState = await JobService.getQueuePosition({
workspace: workspace,
scheduledFor: scheduledFor
})
} catch (error) {
console.error('Failed to fetch queue position:', error)
queueState = undefined
} finally {
fetchingQueuePosition = false
}
}
</script>
{#if isScheduledForLater && scheduledFor != undefined}
<div class="text-xs ml-4">
<span class="text-blue-600">Scheduled for <b>{displayDate(new Date(scheduledFor))}</b></span>
</div>
{:else if queueState}
<div class="text-xs ml-4">
<span class="text-orange-600">Queue position: <b>{queueState.position}</b></span>
{#if !minimal}
<span class="ml-2 text-primary">(Waiting for an available worker)</span>
{/if}
</div>
{/if}