mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-23 08:03:33 +00:00
* feat: responsive modal step panel for the flow editor in sessions On narrow layouts the flow editor's step-details pane opens as a modal (double-click a graph node) instead of a split pane, with a dock/float toggle. Scoped to sessions via allowModalPanel; the full-page editor is unchanged. - FlowEditor: modal/docked modes gated by mount width + allowModalPanel, small header (step-id Badge + subtle dock/close), standing double-click hint, and a per-step hint in the name tooltip - selectionManager: onSelectIntent hook so flow-level panels (settings, input, triggers…) open the modal on single click - PropPickerWrapper: collapse the prop picker until connect and animate it in via AnimatedPane (runs-page pattern), no blue connect ring in modal mode - StepInputGen: drop the TAB/Wand autocompletion button + spinner (feature still works via focus + Tab) - InputTransformForm: decouple the Help dropdown from the AI suggestion - FlowModuleHeader: move 'Save to workspace' into an ellipsis dropdown Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: loop editor rendering and nested splitpanes splitters in the sessions modal - Loop iterator/parallelism: keep the picker split pane (forceExpanded) so the editor fills its box and the picker shows; the collapse-until-connect mode stays for the step inputs - Remove the intrusive AI TAB/Wand autocompletion button from IteratorGen (generation still runs headless via focus + Tab) - Size the iterator connect plug and restyle the loop header/labels/toggles - Scope the global `.splitter-hidden` splitter-hiding rule to direct children so it no longer leaks into nested Splitpanes under the sessions preview Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: redesign flow step advanced settings as a single toggle-first column Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: taller step test pane by default and restyle advanced section titles Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: show flow run-settings params disabled when a setting is toggled off Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column for-loop panel reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column while-loop panel reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: single-column branch panels reusing the run-settings accordion Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: auto-open modal panel when creating an AI agent tool Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: redesign branch panels with card layout and shared predicate editor Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: remove per-setting status badges from flow map nodes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync package-lock after windmill-utils-internal bump Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: polish prop-picker plug button and branch panel layouts * fix: persist skip-if-stopped toggles in early stop settings * fix: open the step panel modal on demand and cap its width * fix: restore graph step setting badges, strip panel header chips instead * feat: docked panel header with detach action and open-details step menu * feat: width-based panel mode on every surface with inline detach action * refactor: single source for flow step settings and their defaults * docs: pin flow editor vocabulary in CONTEXT.md * fix: open the trigger panel on double click or a specific trigger * fix: keep module pickers inside their pane and dismissable * fix: drop the misleading chevron on the MCP tool entry * fix: resolve flow approvals against the job's workspace, not the nav one * refactor: derive the approval workspace from the job, not from callers * fix: restore S3 snippets and gate params while their setting is off * fix: restore branch mock controls and address review findings * chore: drop stray debug log from the flow map item * feat: pinned output section for loop and branch panels * fix: open the panel for deliberate navigation from the flow header * perf: mount branch predicate editors on demand * fix: skip predicate picker previews the previous step's result * fix: flow-level graph nodes open their panel on a single click * fix: open the step panel for AI chat selections, not for undo * chore: drop dead console.log and duplicated modalPanel doc * fix: re-sync expression editors and scope error-handler settings * fix: match the failure module exactly and ignore unselectable nodes * fix: keep concurrency editable, honour module cache_ttl, tighten panel ids * fix: open panel from indirect selections, use presence for value-driven toggles * fix: don't open settings on error-handler delete, flush editors on unmount * fix: guard editor destroy flush, keep retry kind reachable * refactor: name the run settings panel after the domain vocabulary * fix: only write editor flushes to the step they belong to * fix: bind step panels by id so a delete can't retarget editor writes * fix: don't let the trigger picker's escape close the drawer beneath it * docs: condense two comments to the constraint they record * fix: arbitrate escape through the overlay stack instead of deferring to it * fix: key nested step blocks by identity so anchored bindings can't go stale * fix: untrack the overlay-stack push and drop the frozen branch binding * chore: state the escape rationale once, key branch lists, format * fix: let the topmost overlay own escape instead of the graph * fix: keep the dynamic-input help box out of static template fields * fix: restore the graph connect on the for-loop iterator * fix: end connect mode with the modal and keep it to docked panels * fix: never enter graph connect mode from the modal panel * fix: reveal inserted steps, restore editor pane size, unleak the drawer stack * fix: keep the enable-AI popover reachable in session panes * feat: add the connect policy and its single armed slot * refactor: one picker for every expression input * refactor: route every connect through one armed slot * fix: give every connect button the same footprint * fix: keep the connect ring from showing through the button * fix: keep flow card actions right-aligned beside the detach button * fix: give the connect ring an opaque ground to mask against * feat: dock the panel back without reopening it * feat: dock the panel from the graph control bar * style: round the graph control bar and size its glyphs * style: customize the graph controls through their supported api * style: build the graph control bar from lucide icons * fix: use the graph's tooltip component in the zoom controls * style: pad the graph controls and enlarge their glyphs * style: pad the graph controls and put dock at the bar's end * refactor: give settings rows the same popover picker as other expressions * fix: pass the wrapper's pickable properties to nested inputs * refactor: stack step settings and render every expression through the step input form * feat: split loop panels into tabs and rework the approval form * feat: anchor drawers to their host pane and give them a size floor * fix: mark the loop iterator expression as required * refactor: badge ee-only toggles instead of a warning line * fix: flag an empty loop iterator expression as an error * refactor: pick the early-stop flow status from one toggle group * fix: keep parallel loops uncapped unless a limit is opted into * fix: scope the overlay stack to its host and disarm connect on dismissal * fix: anchor the trigger picker to its host pane * feat: move diff into the menu when the top bar is narrow * fix: gate the result logs toggle to the graph popover * feat: raise the modal-panel breakpoint to 1280 * fix: anchor flow editor popovers and fullscreen to their host pane * fix: anchor overlays to their host pane and mute them when hidden * fix: portal hosted modals and menus into the pane they anchor to * fix: keep non-listening dialogs off the overlay stack * fix: drop the topmost gate from confirmation dialogs * fix: silence overlays in a collapsed preview panel * feat: rework the branch panels with tabs, reordering and add/delete * refactor: fold the detached-panel chrome into the card header * fix: give every flow panel a titled card header * fix: stop the step panel oscillating on an auto-height editor * feat: consolidate script panel actions and restore branch predicate AI * fix: restore the logs toggle on the flow result popover * fix: collapse the idle property picker in modal step panels * fix: stop the docked pane scrolling alongside its panel * fix: space the last settings row off the panel bottom * revert: always show the property picker pane in step panels * chore: keep the inline script AI button identical to main * fix: ask for AI input suggestions on click, not on hover * fix: keep graph connects armed and remount the parallelism input * style: reveal the predicate AI button on row hover * style: give branch cards a handle and delete column * refactor: arbitrate flow overlay escape through Disposable * fix: give the popover picker its results and re-narrow the EE badge * docs: correct loopSubset and guard the modal width measurement * fix: insert picked properties at the cursor in expression inputs * fix: give the expanded-subflow panel the shared header chrome * style: rename the suspend setting to Suspend until approval/resume * feat: open a step's modal when clicking the step already selected * feat: add an auto/attached/detached toggle for the step panel * refactor: pick the step panel's placement from one named menu * refactor: keep the panel-mode module's exports to what is consumed * feat: show each configured setting's value on its badge * fix: carry the suspend rename into the step settings registry * docs: name both gestures in the step explore hint * test: pin where the step panel goes for a given width and preference --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
471 lines
15 KiB
Svelte
471 lines
15 KiB
Svelte
<script lang="ts">
|
|
import type { Retry, FlowModule } from '$lib/gen'
|
|
import { SecondsInput } from '$lib/components/common'
|
|
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
|
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
|
import { enterpriseLicense } from '$lib/stores'
|
|
import { untrack, getContext } from 'svelte'
|
|
import Toggle from '$lib/components/Toggle.svelte'
|
|
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
|
import InputTransformForm from '$lib/components/InputTransformForm.svelte'
|
|
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
|
import { emptySchema } from '$lib/utils'
|
|
import type { FlowEditorContext } from '../types'
|
|
import { getStepPropPicker } from '../previousResults'
|
|
import { NEVER_TESTED_THIS_FAR } from '../models'
|
|
import { validateRetryConfig } from '$lib/utils'
|
|
import EEOnly from '$lib/components/EEOnly.svelte'
|
|
import { slideDynamic } from '$lib/transitions'
|
|
import Alert from '$lib/components/common/alert/Alert.svelte'
|
|
import { SAME_WORKER_INCOMPATIBLE_MSG } from '../utils.svelte'
|
|
|
|
interface Props {
|
|
flowModuleRetry: Retry | undefined
|
|
disabled?: boolean
|
|
flowModule?: FlowModule
|
|
isAgentTool?: boolean
|
|
}
|
|
|
|
let {
|
|
flowModule = $bindable(),
|
|
flowModuleRetry = $bindable(),
|
|
disabled = false,
|
|
isAgentTool = false
|
|
}: Props = $props()
|
|
|
|
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
|
|
const { flowStateStore, flowStore, previewArgs } = flowEditorContext || {
|
|
flowStateStore: null,
|
|
flowStore: null,
|
|
previewArgs: null
|
|
}
|
|
|
|
let delayType = $state() as 'disabled' | 'constant' | 'exponential' | undefined
|
|
let retryIfEditor: SimpleEditor | undefined = $state(undefined)
|
|
|
|
// `retry_if` is stored as a bare `{ expr }`, so the form is told its kind through
|
|
// `argType` rather than inferring one from the value.
|
|
let predicateSchema = $state(emptySchema())
|
|
predicateSchema.properties['retry_if'] = { type: 'boolean' }
|
|
let loaded = $state(false)
|
|
|
|
let stepPropPicker = $derived(
|
|
flowModule && flowStateStore?.val && flowStore?.val && previewArgs?.val
|
|
? getStepPropPicker(
|
|
flowStateStore.val,
|
|
undefined,
|
|
undefined,
|
|
flowModule.id,
|
|
flowStore.val,
|
|
previewArgs.val,
|
|
false
|
|
)
|
|
: null
|
|
)
|
|
|
|
// `flowModule` is only set when editing a flow step: the trigger/schedule usages of this
|
|
// component share the flow editor context but are unaffected by `same_worker`, and so are
|
|
// agent tools, which never go through the flow scheduler.
|
|
let sameWorker = $derived(
|
|
Boolean(flowModule && !isAgentTool && flowStore?.val?.value?.same_worker)
|
|
)
|
|
let isDisabled = $derived(disabled || sameWorker)
|
|
|
|
let isRetryConditionEnabled = $derived(Boolean(flowModuleRetry?.retry_if))
|
|
// When retries are toggled off the config is still shown (read-only) so the row
|
|
// isn't empty: fall back to sensible defaults and force the "constant" tab.
|
|
let retriesOff = $derived(!(delayType === 'constant' || delayType === 'exponential'))
|
|
let displayDelayType = $derived(delayType === 'exponential' ? 'exponential' : 'constant')
|
|
// The shown branch's config must actually exist, or its inputs would bind to the
|
|
// display fallback below and silently discard every keystroke.
|
|
let shownBranchMissing = $derived(
|
|
displayDelayType === 'constant' ? !flowModuleRetry?.constant : !flowModuleRetry?.exponential
|
|
)
|
|
let cfgDisabled = $derived(disabled || retriesOff || shownBranchMissing)
|
|
// The kind selector never takes `shownBranchMissing`: switching kind is how the user
|
|
// reseeds a branch, so disabling it there would strand them with no way back.
|
|
let kindDisabled = $derived(disabled || retriesOff)
|
|
|
|
// An external rewrite (AI chat, a session edit) can swap the retry kind under a mounted
|
|
// row. Follow it, or the row would disable itself over a config that is perfectly valid.
|
|
$effect(() => {
|
|
const r = flowModuleRetry
|
|
if (!r) return
|
|
untrack(() => {
|
|
if (delayType === 'constant' && !r.constant && r.exponential) {
|
|
delayType = 'exponential'
|
|
} else if (delayType === 'exponential' && !r.exponential && r.constant) {
|
|
delayType = 'constant'
|
|
} else if (delayType === 'disabled' && (r.constant || r.exponential)) {
|
|
// Retries added from outside: without this the row keeps reading "off" while
|
|
// rendering the incoming values greyed out, and toggling on would overwrite them.
|
|
delayType = r.constant ? 'constant' : 'exponential'
|
|
}
|
|
})
|
|
})
|
|
let displayRetry = $derived(
|
|
flowModuleRetry ?? {
|
|
constant: { attempts: 1, seconds: 5 },
|
|
exponential: { attempts: 1, multiplier: 1, seconds: 5, random_factor: 0 }
|
|
}
|
|
)
|
|
// Always-defined sub-configs so the read-only off-state can bind without
|
|
// undefined checks; when the real config exists these are the same refs.
|
|
let displayConstant = $derived(displayRetry.constant ?? { attempts: 1, seconds: 5 })
|
|
let displayExponential = $derived(
|
|
displayRetry.exponential ?? { attempts: 1, multiplier: 1, seconds: 5, random_factor: 0 }
|
|
)
|
|
// Only feed the preview the branch that's actually shown, so the off-state
|
|
// defaults don't render a bogus combined schedule.
|
|
let previewRetry = $derived(
|
|
retriesOff
|
|
? displayDelayType === 'constant'
|
|
? { constant: displayRetry.constant }
|
|
: { exponential: displayRetry.exponential }
|
|
: flowModuleRetry
|
|
)
|
|
let result = $derived(
|
|
flowModule && flowStateStore?.val
|
|
? (flowStateStore.val[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR)
|
|
: NEVER_TESTED_THIS_FAR
|
|
)
|
|
|
|
let validationError = $derived.by(() => {
|
|
return validateRetryConfig(flowModuleRetry)
|
|
})
|
|
|
|
function setConstantRetries() {
|
|
flowModuleRetry = {
|
|
...flowModuleRetry,
|
|
constant: {
|
|
attempts: 1,
|
|
seconds: 5
|
|
}
|
|
}
|
|
}
|
|
|
|
function setExponentialRetries() {
|
|
flowModuleRetry = {
|
|
...flowModuleRetry,
|
|
exponential: {
|
|
attempts: 1,
|
|
multiplier: 1,
|
|
seconds: 5,
|
|
random_factor: 0
|
|
}
|
|
}
|
|
}
|
|
|
|
function initialLoad() {
|
|
// Presence, not attempts > 0: a retry block with zero attempts is still
|
|
// configured, and flowStepSettings describes it that way.
|
|
delayType =
|
|
flowModuleRetry?.constant != undefined
|
|
? 'constant'
|
|
: flowModuleRetry?.exponential != undefined
|
|
? 'exponential'
|
|
: 'disabled'
|
|
loaded = true
|
|
}
|
|
|
|
function resetDelayType() {
|
|
delayType = 'disabled'
|
|
}
|
|
|
|
$effect(() => {
|
|
flowModuleRetry === undefined && resetDelayType()
|
|
})
|
|
$effect(() => {
|
|
!loaded && untrack(() => initialLoad())
|
|
})
|
|
|
|
const u32Max = 4294967295
|
|
</script>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
{#if sameWorker}
|
|
<Alert type="warning" size="xs" title="Disabled by the shared directory">
|
|
{SAME_WORKER_INCOMPATIBLE_MSG} Disable `Same Worker` in the flow settings to use retries.
|
|
</Alert>
|
|
{/if}
|
|
<Toggle
|
|
size="xs"
|
|
textClass="text-xs font-normal text-primary"
|
|
disabled={isDisabled}
|
|
checked={delayType === 'constant' || delayType === 'exponential'}
|
|
on:change={() => {
|
|
if (delayType === 'constant' || delayType === 'exponential') {
|
|
flowModuleRetry = undefined
|
|
delayType = 'disabled'
|
|
} else {
|
|
setConstantRetries()
|
|
delayType = 'constant'
|
|
}
|
|
}}
|
|
options={{
|
|
right: 'Retry on failure',
|
|
rightTooltip:
|
|
'Upon error this step is retried with a delay and a maximum number of attempts as defined below.',
|
|
rightDocumentationLink: 'https://www.windmill.dev/docs/flows/retries'
|
|
}}
|
|
/>
|
|
|
|
{#if !retriesOff && !sameWorker}
|
|
<div class="flex flex-col gap-6 pl-9" transition:slideDynamic>
|
|
<ToggleButtonGroup
|
|
selected={retriesOff ? displayDelayType : delayType}
|
|
disabled={kindDisabled}
|
|
class={`${kindDisabled ? 'disabled' : ''}`}
|
|
on:selected={(e) => {
|
|
flowModuleRetry = undefined
|
|
if (e.detail === 'constant') {
|
|
setConstantRetries()
|
|
delayType = 'constant'
|
|
} else if (e.detail === 'exponential') {
|
|
setExponentialRetries()
|
|
delayType = 'exponential'
|
|
}
|
|
}}
|
|
>
|
|
{#snippet children({ item })}
|
|
<ToggleButton value="constant" label="Constant" {item} />
|
|
<ToggleButton value="exponential" label="Exponential" {item} />
|
|
{/snippet}
|
|
</ToggleButtonGroup>
|
|
|
|
{#snippet retryConditionToggle()}
|
|
<Toggle
|
|
size="xs"
|
|
textClass="text-xs font-normal text-primary"
|
|
disabled={cfgDisabled}
|
|
checked={isRetryConditionEnabled}
|
|
on:change={() => {
|
|
if (!flowModuleRetry) {
|
|
return
|
|
}
|
|
if (isRetryConditionEnabled && flowModuleRetry.retry_if) {
|
|
const { retry_if, ...rest } = flowModuleRetry
|
|
flowModuleRetry = rest
|
|
} else {
|
|
flowModuleRetry = {
|
|
...flowModuleRetry,
|
|
retry_if: {
|
|
expr: 'error && error.name !== "PERMANENT_FAILURE"'
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
options={{
|
|
right: 'Conditional retry',
|
|
rightTooltip:
|
|
'Optional condition to determine when to retry. Expression should return true to retry, false to skip retry. If not specified, retries on any failure within the configured attempt limits.'
|
|
}}
|
|
/>
|
|
{/snippet}
|
|
|
|
{#if stepPropPicker}
|
|
<PropPickerWrapper
|
|
popover={true}
|
|
flow_input={stepPropPicker.pickableProperties.flow_input}
|
|
notSelectable
|
|
{result}
|
|
displayContext={false}
|
|
pickableProperties={stepPropPicker.pickableProperties}
|
|
on:select={({ detail }) => {
|
|
retryIfEditor?.insertAtCursor(detail)
|
|
retryIfEditor?.focus()
|
|
}}
|
|
>
|
|
<InputTransformForm
|
|
bind:arg={
|
|
() => flowModuleRetry?.retry_if,
|
|
(v) => {
|
|
if (flowModuleRetry) flowModuleRetry.retry_if = v
|
|
}
|
|
}
|
|
argName="retry_if"
|
|
argType="javascript"
|
|
collapsed={!isRetryConditionEnabled}
|
|
animateAppear
|
|
header={retryConditionToggle}
|
|
noDynamicToggle
|
|
schema={predicateSchema}
|
|
previousModuleId={undefined}
|
|
pickableProperties={stepPropPicker.pickableProperties}
|
|
extraLib={`declare const result = ${JSON.stringify(result)};` +
|
|
`\ndeclare const flow_input = ${JSON.stringify(stepPropPicker.pickableProperties.flow_input || {})};`}
|
|
bind:editor={retryIfEditor}
|
|
/>
|
|
</PropPickerWrapper>
|
|
{:else}
|
|
<div class="flex flex-col gap-2">
|
|
{@render retryConditionToggle()}
|
|
{#if flowModuleRetry?.retry_if}
|
|
<div class="border rounded-md overflow-auto w-full">
|
|
<SimpleEditor
|
|
lang="javascript"
|
|
bind:code={flowModuleRetry.retry_if.expr}
|
|
class="few-lines-editor"
|
|
extraLib={`declare const result = ${JSON.stringify(result)};`}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="flex h-[calc(100%-22px)]">
|
|
<div class="w-1/2 h-full overflow-auto pr-2">
|
|
{#if displayDelayType === 'constant'}
|
|
<div class="text-xs font-bold !mt-2">Attempts</div>
|
|
<div class="flex gap-1">
|
|
<input
|
|
max={u32Max.toString()}
|
|
disabled={cfgDisabled}
|
|
bind:value={displayConstant.attempts}
|
|
type="number"
|
|
/>
|
|
<button
|
|
class="text-xs"
|
|
disabled={cfgDisabled}
|
|
onclick={() =>
|
|
flowModuleRetry?.constant && (flowModuleRetry.constant.attempts = u32Max)}
|
|
>max</button
|
|
>
|
|
</div>
|
|
<div class="text-xs font-bold !mt-4">Delay</div>
|
|
<SecondsInput disabled={cfgDisabled} bind:seconds={displayConstant.seconds} />
|
|
{:else if displayDelayType === 'exponential'}
|
|
<div class="text-xs font-bold !mt-2">Attempts</div>
|
|
<div class="flex gap-1">
|
|
<input
|
|
max="100"
|
|
disabled={cfgDisabled}
|
|
bind:value={displayExponential.attempts}
|
|
type="number"
|
|
/>
|
|
<button
|
|
class="text-xs"
|
|
disabled={cfgDisabled}
|
|
onclick={() =>
|
|
flowModuleRetry?.exponential && (flowModuleRetry.exponential.attempts = 100)}
|
|
>max</button
|
|
>
|
|
</div>
|
|
<div class="text-xs font-bold !mt-2">Multiplier</div>
|
|
<span class="text-xs text-primary">delay = multiplier * base ^ (number of attempt)</span
|
|
>
|
|
<input
|
|
disabled={cfgDisabled}
|
|
bind:value={displayExponential.multiplier}
|
|
type="number"
|
|
/>
|
|
<div class="text-xs font-bold !mt-2">Base (in seconds)</div>
|
|
<input
|
|
disabled={cfgDisabled}
|
|
bind:value={displayExponential.seconds}
|
|
type="number"
|
|
step="1"
|
|
min="0"
|
|
class={validationError ? 'border-red-500' : ''}
|
|
/>
|
|
{#if validationError}
|
|
<span class="text-xs text-red-500">{validationError}</span>
|
|
{:else}
|
|
<span class="text-xs text-tertiary"
|
|
>Must be ≥ 1. A base of 0 would cause immediate retries.</span
|
|
>
|
|
{/if}
|
|
<div class="text-xs font-bold !mt-2">Randomization factor (percentage)</div>
|
|
<div class="flex w-full gap-4">
|
|
{#if !$enterpriseLicense}
|
|
<EEOnly />
|
|
{/if}
|
|
<input
|
|
disabled={cfgDisabled || !$enterpriseLicense}
|
|
type="range"
|
|
min={0}
|
|
max={100}
|
|
step={5}
|
|
bind:value={displayExponential.random_factor}
|
|
/>
|
|
<div class="w-20">
|
|
<input
|
|
disabled={true}
|
|
bind:value={displayExponential.random_factor}
|
|
type="number"
|
|
step={5}
|
|
min={0}
|
|
max={100}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<div class="w-1/2 h-full overflow-auto pl-2">
|
|
{#if true}
|
|
{@const { attempts: cAttempts, seconds: cSeconds } = previewRetry?.constant || {}}
|
|
{@const {
|
|
attempts: eAttempts,
|
|
seconds: eSeconds,
|
|
multiplier,
|
|
random_factor
|
|
} = previewRetry?.exponential || {}}
|
|
{@const cArray = Array.from({ length: Math.min(cAttempts || 0, 100) }, () => cSeconds)}
|
|
{@const eArray = Array.from(
|
|
{ length: Math.min(eAttempts || 0, 100) },
|
|
(_, i) => (multiplier || 0) * (eSeconds || 0) ** (i + cArray.length + 1)
|
|
)}
|
|
{@const array = [...cArray, ...eArray]}
|
|
<div class="bg-surface-secondary border rounded px-4 py-2">
|
|
<div class="text-xs font-medium mb-2">Retry attempts</div>
|
|
{#if array.length > 0}
|
|
<table class="text-xs">
|
|
<thead>
|
|
<tr>
|
|
<td class="font-semibold pr-1 pb-1">1:</td>
|
|
<td class="pb-1"
|
|
>After {array[0]} second{array[0] === 1 ? '' : 's'}
|
|
{#if (random_factor ?? 0) > 0}(+/- {((array[0] ?? 0) *
|
|
(random_factor ?? 0)) /
|
|
100}
|
|
seconds){/if}</td
|
|
>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each array.slice(1, 100) as delay, i}
|
|
{@const index = i + 2}
|
|
<tr>
|
|
<td class="font-semibold pr-1 align-top">{index}:</td>
|
|
<td class="pb-1 whitespace-nowrap">
|
|
{delay} second{delay === 1 ? '' : 's'}
|
|
{#if (random_factor ?? 0) > 0}(+/- {((delay ?? 0) *
|
|
(random_factor ?? 0)) /
|
|
100}
|
|
seconds){/if}
|
|
after attempt #{index - 1}
|
|
{#if i > cArray.length - 2}
|
|
<span class="text-gray-400 pl-2">
|
|
({multiplier} * {eSeconds}<sup>{index}</sup>)
|
|
</span>
|
|
{/if}
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
{#if (cAttempts ?? 0) > 100 || (eAttempts ?? 0) > 100}
|
|
<tr>
|
|
<td class="font-semibold pr-1 align-top">...</td>
|
|
<td class="pb-1">...</td>
|
|
</tr>
|
|
{/if}
|
|
</tbody>
|
|
</table>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|