Files
windmill/frontend/src/lib/components/AutoscalingConfigEditor.svelte
T
Ruben Fiszel 3c99b3fdc7 feat: migrate to svelte5 + vite6 (#4813)
* runs on svelte 5

* Line component from svelte-chartjs

* Replaced all svelte-chartjs occurrences with custom wrapper

* Fix props mistake

* Fix illegal table structures

* self-closing-tags fix

* aria labels

* Fixed trivial warnings and errors

* @tanstack/svelte-table fix

* upgrade to vite 6

* svelte-kit sync before running svelte-check

* Remove on:clear which is actually on:removeAll and already handled by on:change

* fix worker tags not displaying in Autoscaling

* Try to fix svelte-kit sync not working during CI

* remove warnings

* Fix add flow page crashing

* access worldStore before assignment fix

* fix infinite recursions in App Editor

* Replaced JSON.stringify with proper deepEqual

* component mount api changed (no longer classes)

* fix ci errors

* Fix infinite loops in background runnable panel

* factored effect on deep equal logic in onObjChange

* fix "Add" not working in AgGrid Table

* Replaced legacy component.$set api

* Fix multiselect infinite value reaction

* Fix flow input fields resetting when opening their edit tab

* fix date input resetting when typing year

* Remove !p-0 affecting subgrid dotted borders

* fix missing debounceTemplate causing hundreds of updates

* Fix AgGrid action refreshes and disppearing

* resolve getItems generating random ids every rerun

* fix cannot access items before init

* fix sort lambda arguments being undefined

* Revert "Remove !p-0 affecting subgrid dotted borders"

This reverts commit c62809bb45d682a48376b071680645ed4e1c601b.

* fix input not updating in decision tree editor

* Update frontend/src/lib/components/schema/EditableSchemaWrapper.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Re-added padding affecting subgrid dotted borders (#5479)

* remove !p-0 in preset components

* removed extra padding on accordion tabs subgrid

* Fix non-reactive SchemaForm

* dirty fix for the oneOf bug

* Fix warnings and update svelte-exmarkdown for svelte 5

* fix dnd not working

* don't mount component like objects

---------

Co-authored-by: Diego Imbert <diegoimbert@protonmail.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-02 21:33:50 +00:00

357 lines
10 KiB
Svelte

<script lang="ts">
import type { AutoscalingConfig } from './worker_group'
import Toggle from './Toggle.svelte'
import Section from './Section.svelte'
import Tooltip from './Tooltip.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import { Button } from './common'
import { ExternalLink } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import Label from './Label.svelte'
import MultiSelect from 'svelte-multiselect'
export let config: AutoscalingConfig | undefined
export let worker_tags: string[] | undefined
const dispatch = createEventDispatcher()
let test_input: number = 3
</script>
<div class="flex flex-row gap-16 pt-2">
<div class="space-y-4 flex flex-col gap-1 max-w-xs text-sm">
<h5>Rules</h5>
<Toggle
checked={config?.enabled ?? false}
options={{ right: 'Enabled' }}
on:change={(e) => {
dispatch('dirty')
if (e.detail) {
if (!config) {
config = {
enabled: true,
min_workers: 3,
max_workers: 10,
integration: { type: 'dryrun' }
}
} else {
config.enabled = true
}
} else {
config = {
...(config ?? {
min_workers: 3,
max_workers: 10,
integration: { type: 'dryrun' }
}),
enabled: false
}
}
}}
/>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Min # of Workers
{#if config !== undefined}
<input on:input={() => dispatch('dirty')} type="number" bind:value={config.min_workers} />
{#if config.min_workers !== undefined && config.min_workers != undefined && config.min_workers > config.max_workers}
<div class="text-red-600 text-xs whitespace-nowrap"
>Minimum cannot be {'>'} to Maximum</div
>
{/if}
{:else}
<input type="number" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Max # of Workers
{#if config !== undefined}
<input on:input={() => dispatch('dirty')} type="number" bind:value={config.max_workers} />
{:else}
<input type="number" disabled />
{/if}
</label>
<div class="p-2">
<Section label="Advanced" small collapsable={true}>
<div class="flex flex-col gap-2 text-2xs">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Cooldown seconds after an incremental scale-in/out
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
step="1"
min="30"
placeholder="300"
bind:value={config.cooldown_seconds}
/>
{:else}
<input type="number" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Cooldown seconds after a full scale out
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
step="1"
min="30"
placeholder="1500"
bind:value={config.full_scale_cooldown_seconds}
/>
{:else}
<input type="number" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Num jobs waiting to trigger an incremental scale-out
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
bind:value={config.inc_scale_num_jobs_waiting}
placeholder="1"
/>
{:else}
<input type="number" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Num jobs waiting to trigger a full scale out <Tooltip
>Default: max_workers, full scale out = scale out to max workers</Tooltip
>
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
placeholder="max workers"
bind:value={config.full_scale_jobs_waiting}
/>
{:else}
<input type="number" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Occupancy rate % threshold to go below to trigger a scale-in (decrease) <Tooltip
>Default: 25%, need to go below average of all of 15s, 5m and 30m occupancy rates</Tooltip
>
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
step="1"
min="0"
max="100"
placeholder="25"
bind:value={config.dec_scale_occupancy_rate}
/>
{:else}
<input type="number" step="0.01" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Occupancy rate threshold to exceed to trigger an incremental scale-out (increase) <Tooltip
>Default: 75%, need to exceed average of all of 15s, 5m and 30m occupancy rates</Tooltip
>
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
step="1"
min="0"
max="100"
placeholder="75"
bind:value={config.inc_scale_occupancy_rate}
/>
{:else}
<input type="number" step="0.01" disabled />
{/if}
</label>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
Num workers to scale-in/out by when incremental <Tooltip
>Default: (max_workers - min_workers) / 5</Tooltip
>
{#if config !== undefined}
<input
on:input={() => dispatch('dirty')}
type="number"
step="1"
min="1"
placeholder="(max_workers - min_workers) / 5"
bind:value={config.inc_num_workers}
/>
{:else}
<input type="number" disabled />
{/if}
</label>
<Label label="Custom tags to autoscale on">
<svelte:fragment slot="header">
<Tooltip>
By default, autoscaling will apply to the tags the worker group is assigned to but
you can override this here.
</Tooltip>
</svelte:fragment>
{#if config}
{#if config.custom_tags}
<MultiSelect
outerDivClass="text-secondary !bg-surface-disabled !border-0"
selected={config.custom_tags}
on:change={(e) => {
console.log(e.detail.type, config?.custom_tags)
if (e.detail && config?.custom_tags) {
if (e.detail.type === 'add') {
config.custom_tags = [
...config.custom_tags,
...(e.detail.option ? [e.detail.option.toString()] : [])
]
} else if (e.detail.type === 'remove') {
config.custom_tags = config.custom_tags.filter((t) => t !== e.detail.option)
if (config?.custom_tags && config.custom_tags.length == 0) {
config.custom_tags = undefined
}
} else if (e.detail.type === 'removeAll') {
config.custom_tags = undefined
} else {
console.error(
`Priority tags multiselect - unknown event type: '${e.detail.type}'`
)
}
dispatch('dirty')
}
}}
options={worker_tags ?? []}
selectedOptionsDraggable={false}
ulOptionsClass={'!bg-surface-secondary'}
placeholder="Tags"
/>
{:else}
<Button
color="light"
size="xs"
variant="contained"
on:click={() => {
if (config) {
config.custom_tags = []
dispatch('dirty')
}
}}>Add custom tags</Button
>
{/if}
{/if}
</Label>
</div>
</Section>
</div>
</div>
<div class="flex flex-col gap-1 max-w-xs text-sm">
<h5>Integration</h5>
{#if config?.integration}
<ToggleButtonGroup
on:selected={(e) => dispatch('dirty')}
bind:selected={config.integration.type}
class="mb-4 mt-2"
let:item
>
<ToggleButton
value="dryrun"
label="Dry run"
tooltip="See autoscaling events but not actual scaling actions will be performed"
{item}
/>
<ToggleButton
value="script"
label="Custom script"
tooltip="Run a custom script to scale your worker group"
{item}
/>
<ToggleButton disabled value="ecs" label="ECS (soon)" {item} />
<ToggleButton disabled value="nomad" label="Nomad (soon)" {item} />
<ToggleButton disabled value="kubernetes" label="Kubernetes (soon)" {item} />
</ToggleButtonGroup>
{#if config.integration.type === 'script'}
<label>
Script path on the 'admins' workspace
<input
on:input={() => dispatch('dirty')}
type="text"
bind:value={config.integration.path}
/>
</label>
<label>
Custom tag for executing script (optional)
{#if config.integration.tag}
<input
on:input={() => dispatch('dirty')}
type="text"
bind:value={config.integration.tag}
/>
{:else}
<Button
color="light"
size="xs"
variant="contained"
on:click={() => {
if (config?.integration?.type === 'script') {
config.integration.tag = 'bash'
dispatch('dirty')
}
}}>Set tag</Button
>
{/if}
</label>
<div class="flex mt-6 gap-2">
<Button
color="dark"
target="_blank"
endIcon={{ icon: ExternalLink }}
href="/scripts/add?hub=hub%2F9204%2Fhelper%2FScale%20a%20worker%20group%20deployed%20as%20a%20kubernetes%20service&workspace=admins"
>Create from template</Button
>
<Button
color="dark"
target="_blank"
href={`/runs/${config.integration.path}?workspace=admins`}
endIcon={{ icon: ExternalLink }}
>
See jobs
</Button>
</div>
<div class="flex flex-row gap-2 mt-4">
<Button color="light" size="xs" variant="contained">Test scaling</Button>
<div class="flex text-xs flex-row gap-2 items-center">
<input class="!w-16" type="number" bind:value={test_input} />
workers
</div>
</div>
{/if}
{:else}
<ToggleButtonGroup selected={'script'} disabled class="mb-4 mt-2" let:item>
<ToggleButton value="dryrun" label="Dry run" {item} />
<ToggleButton value="script" label="Custom script" {item} />
<ToggleButton value="ecs" label="ECS (soon)" {item} />
<ToggleButton value="nomad" label="Nomad (soon)" {item} />
<ToggleButton value="kubernetes" label="Kubernetes (soon)" {item} />
</ToggleButtonGroup>
<label>
Script path on the 'admins' workspace
<input type="text" disabled />
</label>
{/if}
</div>
</div>