feat(frontend): migrate toggle to melt (4/4) (#5329)

* use melt menu in sidebar

* stop keyboard navigation for disabled items

* use melt menu for FavoriteMenu and WorkspaceMenu

* fix popover placement for menuButton

* use melt menu for operator menu

* fix notification

* fix operator menu

* Use melt menu in FlowJobsMenu

* use melt menu for AppMenu

* clean code

* clean code

* add use clickOutside option to Menu

* use pointerdown_outside

* use pointerdown_outside

# Conflicts:
#	frontend/src/lib/components/meltComponents/Menu.svelte

* use pointerdown in menus

* add max-h to app dropdown menu

* keep more open in operator menu

* add a MenuItem component

* clean

* nit

* nit

* clean code

* put conditionalMelt as utility function

* remove unused Portal

* Add debounce effect in operator menu

* fix component jumping due to z-index

* format pages

* migrate dropdown to melt

* migrate toggle to melt

* migrate popup to melt popover

* fix missing toggle item

* feat: remove `pip` fallback option for python and ansible (#5186)

* refactor!: Remove `pip` fallback option for python and ansible

BREAKING CHANGE: pip was deprecated since 1.425.0 (2024-11-15)

* fix errors in main.rs

* fix tests

* remove nsjail for pip

* fix imports

* fix compilation error

* reinforce melt types

* fix racing condition issue in closing operator menu

* nit

* fix id conflix with melt element

* nit

* clean code

* use melt dropdown instead of menubar

* prevent modal from closing on click outside button in menu

* Apply automatic changes

* fix nit

* nit

* close dropdown when opening a new one

* replace MenuV2 with melt Menu (1/4) (#5214)

* use melt menu in sidebar

* stop keyboard navigation for disabled items

* use melt menu for FavoriteMenu and WorkspaceMenu

* fix popover placement for menuButton

* use melt menu for operator menu

* fix notification

* fix operator menu

* Use melt menu in FlowJobsMenu

* use melt menu for AppMenu

* clean code

* clean code

* add use clickOutside option to Menu

* use pointerdown_outside

* use pointerdown_outside

# Conflicts:
#	frontend/src/lib/components/meltComponents/Menu.svelte

* use pointerdown in menus

* add max-h to app dropdown menu

* keep more open in operator menu

* add a MenuItem component

* clean

* nit

* nit

* clean code

* put conditionalMelt as utility function

* remove unused Portal

* Add debounce effect in operator menu

* fix component jumping due to z-index

* feat: remove `pip` fallback option for python and ansible (#5186)

* refactor!: Remove `pip` fallback option for python and ansible

BREAKING CHANGE: pip was deprecated since 1.425.0 (2024-11-15)

* fix errors in main.rs

* fix tests

* remove nsjail for pip

* fix imports

* fix compilation error

* reinforce melt types

* fix racing condition issue in closing operator menu

* nit

* fix id conflix with melt element

* nit

* prevent modal from closing on click outside button in menu

---------

Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
# Conflicts:
#	frontend/src/lib/components/meltComponents/MenuItem.svelte
#	frontend/src/lib/utils.ts

* clean

* fix z index and render

* fix initialize of dropdownmenu after melt migration

* feat: add support for | None and Optional in python (#5361)

* feat: add support for | None and Optional in python

* update python parser package

* add local rooting for MenuItem

* fix z index

* clean

* nit

* nit

* clean code

* nit

* nit

* clean code

* reinforce melt types

* wip

* reiforce instance select types for toggleButton

* nit

* fix double event

* fix selectedTable toggle

* fix sqs toggleButton

* fix potential issue with binding in toggleGroup

* Update frontend/src/routes/(root)/(logged)/runs/[...path]/+page.svelte

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

---------

Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
Guilhem
2025-02-26 16:32:40 +00:00
committed by GitHub
parent eff2a4c496
commit 69fc8a98ae
85 changed files with 959 additions and 764 deletions
+5 -5
View File
@@ -1,7 +1,7 @@
declare namespace svelte.JSX {
interface DOMAttributes<T> {
onclick_outside?: CompositionEventHandler<T>
onpointerdown_outside?: (event: CustomEvent) => void
onpointerdown_connecting?: (event: CustomEvent) => void
declare namespace svelteHTML {
interface HTMLAttributes<T> {
'on:click_outside'?: (event: CustomEvent) => void
'on:pointerdown_outside'?: (event: CustomEvent) => void
'on:pointerdown_connecting'?: (event: CustomEvent) => void
}
}
+4 -6
View File
@@ -92,26 +92,24 @@
{/if}
<span class="text-xs mb-1 pt-2 leading-6">Role</span>
<ToggleButtonGroup bind:selected class="mb-4">
<ToggleButtonGroup bind:selected class="mb-4" let:item>
<ToggleButton
value="operator"
size="sm"
label="Operator"
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
{item}
/>
<ToggleButton
position="center"
value="developer"
size="sm"
label="Developer"
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
{item}
/>
<ToggleButton
position="right"
value="admin"
size="sm"
label="Admin"
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
{item}
/>
</ToggleButtonGroup>
<Button
+6 -4
View File
@@ -756,14 +756,16 @@
<div class="flex flex-col gap-2 w-full">
{#if oneOf && oneOf.length >= 2}
<ToggleButtonGroup
bind:selected={oneOfSelected}
on:selected={() => {
value = { label: oneOfSelected }
selected={oneOfSelected}
on:selected={({ detail }) => {
oneOfSelected = detail
value = { label: detail }
redraw += 1
}}
let:item
>
{#each oneOf as obj}
<ToggleButton value={obj.title} label={obj.title} />
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
{/each}
</ToggleButtonGroup>
{#if oneOfSelected}
@@ -52,9 +52,15 @@
<label class="block pb-2">
<div class="flex gap-2 items-end">
<div>
<ToggleButtonGroup bind:selected={value['custom']}>
<ToggleButton value={false} label={'Org'} />
<ToggleButton value={true} label="Custom" />
<ToggleButtonGroup
selected={value['custom'] ? 'custom' : 'org'}
on:selected={({ detail }) => {
value['custom'] = detail === 'custom'
}}
let:item
>
<ToggleButton value="org" label={'Org'} {item} />
<ToggleButton value="custom" label="Custom" {item} />
</ToggleButtonGroup>
</div>
<div class="grow">
@@ -264,28 +264,23 @@
on:selected={(e) => dispatch('dirty')}
bind:selected={config.integration.type}
class="mb-4 mt-2"
let:item
>
<ToggleButton
value="dryrun"
size="sm"
label="Dry run"
tooltip="See autoscaling events but not actual scaling actions will be performed"
{item}
/>
<ToggleButton
value="script"
size="sm"
label="Custom script"
tooltip="Run a custom script to scale your worker group"
{item}
/>
<ToggleButton position="center" disabled value="ecs" size="sm" label="ECS (soon)" />
<ToggleButton position="right" disabled value="nomad" size="sm" label="Nomad (soon)" />
<ToggleButton
position="right"
disabled
value="kubernetes"
size="sm"
label="Kubernetes (soon)"
/>
<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'}
@@ -347,12 +342,12 @@
</div>
{/if}
{:else}
<ToggleButtonGroup selected={'script'} disabled class="mb-4 mt-2">
<ToggleButton value="dryrun" size="sm" label="Dry run" />
<ToggleButton value="script" size="sm" label="Custom script" />
<ToggleButton position="center" value="ecs" size="sm" label="ECS (soon)" />
<ToggleButton position="right" value="nomad" size="sm" label="Nomad (soon)" />
<ToggleButton position="right" value="kubernetes" size="sm" label="Kubernetes (soon)" />
<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>
@@ -157,15 +157,23 @@
<div class="flex flex-col gap-2">
<div class="flex gap-2 flex-wrap sticky top-0 left-0 right-0 bg-surface">
<div class="p-2">
<ToggleButtonGroup bind:selected={searchKind} class="h-10 ">
<ToggleButton small light value="all" label={'All' + counts.all} />
<ToggleButton small light value="scripts" icon={Code2} label={'Scripts' + counts.scripts} />
<ToggleButtonGroup bind:selected={searchKind} class="h-10 " let:item>
<ToggleButton small light value="all" label={'All' + counts.all} {item} />
<ToggleButton
small
light
value="scripts"
icon={Code2}
label={'Scripts' + counts.scripts}
{item}
/>
<ToggleButton
small
light
value="resources"
icon={Boxes}
label={'Resources' + counts.resources}
{item}
/>
<ToggleButton
small
@@ -174,6 +182,7 @@
label={'Flows' + counts.flows}
icon={FlowIcon}
selectedColor="#14b8a6"
{item}
/>
<ToggleButton
small
@@ -182,6 +191,7 @@
label={'Apps' + counts.apps}
icon={LayoutDashboard}
selectedColor="#fb923c"
{item}
/>
</ToggleButtonGroup>
</div>
@@ -53,6 +53,12 @@
$: dbSchema = resourcePath && resourcePath in $dbSchemas ? $dbSchemas[resourcePath] : undefined
$: shouldDisplayError = resourcePath && resourcePath in $dbSchemas && !$dbSchemas[resourcePath]
function handleSelected({ detail }: CustomEvent<string>) {
if (dbSchema && dbSchema.lang !== 'graphql') {
dbSchema.publicOnly = detail === 'dbo'
}
}
</script>
{#if loading}
@@ -86,9 +92,14 @@
</Button>
</svelte:fragment>
{#if dbSchema.lang !== 'graphql' && (dbSchema.schema?.public || dbSchema.schema?.PUBLIC || dbSchema.schema?.dbo)}
<ToggleButtonGroup class="mb-4" bind:selected={dbSchema.publicOnly}>
<ToggleButton value={true} label={dbSchema.schema.dbo ? 'Dbo' : 'Public'} />
<ToggleButton value={false} label="All" />
<ToggleButtonGroup
class="mb-4"
selected={dbSchema.publicOnly ? 'dbo' : 'all'}
on:selected={handleSelected}
let:item
>
<ToggleButton value="dbo" label={dbSchema.schema.dbo ? 'Dbo' : 'Public'} {item} />
<ToggleButton value="all" label="All" {item} />
</ToggleButtonGroup>
{/if}
{#if dbSchema.lang === 'graphql'}
@@ -146,9 +146,9 @@
</Button>
{/if}
<!-- <div>
<ToggleButtonGroup bind:selected={format}>
<ToggleButton light small value={'local'} label="local" />
<ToggleButton light small value={'utc'} label="utc" />
<ToggleButtonGroup bind:selected={format} let:item>
<ToggleButton light small value={'local'} label="local" {item} />
<ToggleButton light small value={'utc'} label="utc" {item} />
</ToggleButtonGroup>
</div> -->
</div>
@@ -402,10 +402,11 @@
on:selected={(ev) => {
globalForceJson = ev.detail === 'json'
}}
let:item
>
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} />
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} {item} />
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} />
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
</ToggleButtonGroup>
</div>
{/if}
@@ -438,16 +439,23 @@
{#if !hideAsJson && !['json', 's3object'].includes(resultKind ?? '') && typeof result === 'object'}<ToggleButtonGroup
class="h-6"
selected={forceJson ? 'json' : resultKind?.startsWith('table-') ? 'table' : 'pretty'}
let:item
on:selected={(ev) => {
forceJson = ev.detail === 'json'
}}
>
{#if ['table-col', 'table-row', 'table-row-object'].includes(resultKind ?? '')}
<ToggleButton class="px-1.5" value="table" label="Table" icon={Table2} />
<ToggleButton class="px-1.5" value="table" label="Table" icon={Table2} {item} />
{:else}
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} />
<ToggleButton
class="px-1.5"
value="pretty"
label="Pretty"
icon={Highlighter}
{item}
/>
{/if}
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} />
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
</ToggleButtonGroup>
{/if}
</div>
@@ -93,7 +93,13 @@
{#if $$slots.buttonReplacement}
<slot name="buttonReplacement" />
{:else}
<Button nonCaptureEvent size="xs" color="light" startIcon={{ icon: MoreVertical }} />
<Button
nonCaptureEvent
size="xs"
color="light"
startIcon={{ icon: MoreVertical }}
btnClasses="bg-transparent"
/>
{/if}
</button>
@@ -465,6 +465,7 @@
<ToggleButtonGroup
tabListClass="flex-wrap"
class="h-auto"
let:item
bind:selected
on:selected={(e) => {
const isS3 = e.detail == 'S3'
@@ -545,7 +546,7 @@
}}
>
{#each [['String', 'string'], ['Number', 'number'], ['Integer', 'integer'], ['Object', 'object'], ['OneOf', 'oneOf'], ['Array', 'array'], ['Boolean', 'boolean'], ['S3 Object', 'S3']] as x}
<ToggleButton value={x[1]} label={x[0]} />
<ToggleButton value={x[1]} label={x[0]} {item} />
{/each}
</ToggleButtonGroup>
</Label>
@@ -184,9 +184,13 @@
</Alert>
<div class="flex items-center gap-1">
<div>
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (ownerItem = '')}>
<ToggleButton value="user" size="xs" label="User" />
<ToggleButton value="group" size="xs" label="Group" />
<ToggleButtonGroup
bind:selected={ownerKind}
on:selected={() => (ownerItem = '')}
let:item
>
<ToggleButton value="user" label="User" {item} />
<ToggleButton value="group" label="Group" {item} />
</ToggleButtonGroup>
</div>
@@ -194,7 +198,9 @@
<AutoComplete
required
noInputStyles
items={ownerKind === 'user' ? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/'+x)) : groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/'+x))}
items={ownerKind === 'user'
? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/' + x))
: groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/' + x))}
bind:selectedItem={ownerItem}
/>
{#if ownerKind == 'group'}
@@ -271,6 +277,7 @@
<div>
<ToggleButtonGroup
selected={role}
let:item
on:selected={async (e) => {
const role = e.detail
// const wasInFolder = (folder?.owners ?? []).includes(folder)
@@ -309,25 +316,25 @@
>
<ToggleButton
value="viewer"
size="xs"
label="Viewer"
tooltip="A viewer of a folder has read-only access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
/>
<ToggleButton
position="center"
value="writer"
size="xs"
label="Writer"
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
{item}
/>
<ToggleButton
position="right"
value="admin"
size="xs"
label="Admin"
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
{item}
/>
</ToggleButtonGroup>
</div>
@@ -150,6 +150,7 @@
<div>
<ToggleButtonGroup
selected={role}
let:item
on:selected={async (e) => {
const role = e.detail
// const wasInGroup = (group?.members ?? []).includes(group)
@@ -215,6 +216,7 @@
size="xs"
label="Member"
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
{item}
/>
<ToggleButton
position="right"
@@ -222,6 +224,7 @@
size="xs"
label="Admin"
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
{item}
/>
{#if role === 'manager'}
<ToggleButton
@@ -230,6 +233,7 @@
label="Manager"
tooltip="A manager of a group can manage the group, adding and removing users and
change their roles. Being a manager does not make you a member"
{item}
/>
{/if}
</ToggleButtonGroup>
@@ -358,6 +358,7 @@
<div>
<ToggleButtonGroup
selected={propertyType}
let:item
on:selected={(e) => {
if (e.detail == propertyType) return
const staticTemplate = isStaticTemplate(inputCat)
@@ -417,9 +418,10 @@
value="static"
size="xs2"
label={'${}'}
{item}
/>
{:else}
<ToggleButton small label="Static" value="static" />
<ToggleButton small label="Static" value="static" {item} />
{/if}
{#if codeInjectionDetected && propertyType == 'static'}
@@ -441,6 +443,7 @@
tooltip="JavaScript expression ('flow_input' or 'results')."
value="javascript"
icon={FunctionSquare}
{item}
/>
{/if}
</ToggleButtonGroup>
@@ -185,12 +185,13 @@
</span>
{/if}
</label>
<ToggleButtonGroup bind:selected={$values[setting.key]}>
<ToggleButtonGroup bind:selected={$values[setting.key]} let:item={toggleButton}>
{#each setting.select_items ?? [] as item}
<ToggleButton
value={item.value ?? item.label}
label={item.label}
tooltip={item.tooltip}
item={toggleButton}
/>
{/each}
</ToggleButtonGroup>
@@ -80,28 +80,26 @@
bind:value={email}
class="mr-4"
/>
<ToggleButtonGroup bind:selected>
<ToggleButtonGroup bind:selected let:item>
<ToggleButton
value="operator"
size="sm"
label="Operator"
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
{item}
/>
<ToggleButton
position="center"
value="developer"
size="sm"
label="Developer"
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
{item}
/>
<ToggleButton
position="right"
value="admin"
size="sm"
label="Admin"
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
{item}
/>
</ToggleButtonGroup>
<Button
@@ -52,9 +52,15 @@
<label class="block pb-2">
<div class="flex gap-2 items-end">
<div>
<ToggleButtonGroup bind:selected={value['custom']}>
<ToggleButton value={false} label={'Org'} />
<ToggleButton value={true} label="Custom" />
<ToggleButtonGroup
selected={value['custom'] ? 'custom' : 'org'}
on:selected={({ detail }) => {
value['custom'] = detail === 'custom'
}}
let:item
>
<ToggleButton value="org" label={'Org'} {item} />
<ToggleButton value="custom" label="Custom" {item} />
</ToggleButtonGroup>
</div>
<div class="grow">
+3
View File
@@ -374,6 +374,7 @@
}
}
}}
let:item
>
<ToggleButton
icon={User}
@@ -383,6 +384,7 @@
value="user"
position="left"
label="User"
{item}
/>
<!-- <ToggleButton light size="xs" value="group" position="center">Group</ToggleButton> -->
<ToggleButton
@@ -393,6 +395,7 @@
value="folder"
position="right"
label="Folder"
{item}
/>
</ToggleButtonGroup>
</div>
+1 -19
View File
@@ -13,7 +13,6 @@
import TimeAgo from './TimeAgo.svelte'
import Popover from './meltComponents/Popover.svelte'
import { autoPlacement } from '@floating-ui/core'
import { Calendar, CornerDownLeft } from 'lucide-svelte'
import RunFormAdvancedPopup from './RunFormAdvancedPopup.svelte'
import { page } from '$app/stores'
@@ -205,24 +204,7 @@
{scheduledForStr ? 'Schedule to run later' : buttonText}
</Button>
<div>
<Popover
floatingConfig={{
middleware: [
autoPlacement({
allowedPlacements: [
'bottom-start',
'bottom-end',
'top-start',
'top-end',
'top',
'bottom'
]
})
]
}}
closeButton
usePointerDownOutside
>
<Popover placement="bottom" closeButton usePointerDownOutside>
<svelte:fragment slot="trigger">
<Button nonCaptureEvent startIcon={{ icon: Calendar }} size="xs" color="light">
Advanced
@@ -335,27 +335,30 @@
{#if linkedSecretCandidates?.includes(argName)}
<div>
<ToggleButtonGroup
selected={linkedSecret == argName}
selected={linkedSecret == argName ? 'secret' : 'inlined'}
on:selected={(e) => {
if (e.detail) {
if (e.detail === 'secret') {
linkedSecret = argName
} else if (linkedSecret == argName) {
linkedSecret = undefined
}
}}
let:item
>
<ToggleButton
value={false}
value="inlined"
size="sm"
label="Inlined"
tooltip="The value is inlined in the resource and thus has no special treatment."
{item}
/>
<ToggleButton
position="right"
value={true}
value="secret"
size="sm"
label="Secret"
tooltip="The value will be stored in a newly created linked secret variable at the same path. That variable can be permissioned differently, will be treated as a secret the UI, operators will not be able to load it and every access will generate a corresponding audit log."
{item}
/>
</ToggleButtonGroup>
</div>{/if}</svelte:fragment
@@ -910,6 +910,7 @@
script.kind = detail
initContent(script.language, detail, template)
}}
let:item
>
{#each scriptKindOptions as { value, title, desc, documentationLink, Icon }}
<ToggleButton
@@ -919,6 +920,7 @@
{documentationLink}
icon={Icon}
showTooltipIcon={Boolean(desc)}
{item}
/>
{/each}
</ToggleButtonGroup>
@@ -50,7 +50,7 @@
items = (
await ScriptService.listScripts({
workspace: $workspaceStore!,
kinds: kinds.join(','),
kinds: kinds.join(',')
})
).map((script) => ({
value: script.path,
@@ -85,9 +85,9 @@
<div class="flex flex-row items-center gap-4 w-full mt-2">
{#if options.length > 1}
<div>
<ToggleButtonGroup bind:selected={itemKind}>
<ToggleButtonGroup bind:selected={itemKind} let:item>
{#each options as [label, value, icon, selectedColor]}
<ToggleButton {icon} {disabled} {value} {label} {selectedColor} />
<ToggleButton {icon} {disabled} {value} {label} {selectedColor} {item} />
{/each}
</ToggleButtonGroup>
</div>
@@ -190,9 +190,15 @@
{#if selectedVersionIndex !== undefined && versions?.slice(selectedVersionIndex + 1).length}
<div class="p-2 flex flex-row items-center gap-2 h-8">
<div class="w-min">
<ToggleButtonGroup bind:selected={showDiff}>
<ToggleButton light small value={false} label="Code" icon={Code} />
<ToggleButton light small value={true} label="Diff" icon={Diff} />
<ToggleButtonGroup
selected={showDiff ? 'diff' : 'code'}
let:item
on:selected={({ detail }) => {
showDiff = detail === 'diff'
}}
>
<ToggleButton light small value="code" label="Code" icon={Code} {item} />
<ToggleButton light small value="diff" label="Diff" icon={Diff} {item} />
</ToggleButtonGroup>
</div>
+10 -5
View File
@@ -123,9 +123,13 @@
{#if own}
<div class="flex flex-row flex-wrap gap-2 items-center">
<div>
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (owner = '')}>
<ToggleButton value="user" size="xs" label="User" />
<ToggleButton value="group" size="xs" label="Group" />
<ToggleButtonGroup
bind:selected={ownerKind}
on:selected={() => (owner = '')}
let:item
>
<ToggleButton value="user" size="xs" label="User" {item} />
<ToggleButton value="group" size="xs" label="Group" {item} />
</ToggleButtonGroup>
</div>
{#key ownerKind}
@@ -163,9 +167,10 @@
}
loadAcls()
}}
let:item
>
<ToggleButton value="viewer" size="xs" label="Viewer" />
<ToggleButton value="writer" size="xs" label="Writer" />
<ToggleButton value="viewer" size="xs" label="Viewer" {item} />
<ToggleButton value="writer" size="xs" label="Writer" {item} />
</ToggleButtonGroup>
</div>
{:else}{write}{/if}</td
@@ -133,9 +133,16 @@
disableVariablePicker = undefined
}
}}
let:item
>
{#each FIELD_SETTINGS as x}
<ToggleButton value={x[1]} label={x[0]} tooltip={x[2]} showTooltipIcon={Boolean(x[2])} />
<ToggleButton
value={x[1]}
label={x[0]}
tooltip={x[2]}
showTooltipIcon={Boolean(x[2])}
{item}
/>
{/each}
</ToggleButtonGroup>
{/if}
@@ -292,15 +292,22 @@
sendUserToast('User updated')
listUsers(activeOnly)
}}
let:item
>
<ToggleButton value={'user'} size="xs" label="User" />
<ToggleButton value={'user'} size="xs" label="User" {item} />
<ToggleButton
value={'devops'}
size="xs"
label="Devops"
tooltip="Devops is a role that grants visibilty similar to that of a super admin, but without giving all rights. For example devops users can see service logs and crtical alerts. You can think of it as a 'readonly' super admin"
{item}
/>
<ToggleButton
value={'super_admin'}
size="xs"
label="Superadmin"
{item}
/>
<ToggleButton value={'super_admin'} size="xs" label="Superadmin" />
</ToggleButtonGroup>
</td>
<td>
@@ -7,8 +7,8 @@
</script>
<div class="max-w-min">
<ToggleButtonGroup bind:selected>
<ToggleButton value="hub" icon={WindmillIcon} label="Hub" />
<ToggleButton value="workspace" icon={Building} label="Workspace" />
<ToggleButtonGroup bind:selected let:item>
<ToggleButton value="hub" icon={WindmillIcon} label="Hub" {item} />
<ToggleButton value="workspace" icon={Building} label="Workspace" {item} />
</ToggleButtonGroup>
</div>
@@ -7,9 +7,9 @@
</script>
<div class="max-w-min">
<ToggleButtonGroup id="flow-editor-flow-providers" bind:selected>
<ToggleButton value="all" label="All" light small />
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" light small />
<ToggleButton value="workspace" icon={Building} label="Workspace" light small />
<ToggleButtonGroup id="flow-editor-flow-providers" bind:selected let:item>
<ToggleButton value="all" label="All" light small {item} />
<ToggleButton value="hub" icon={WindmillIcon2} label="Hub" light small {item} />
<ToggleButton value="workspace" icon={Building} label="Workspace" light small {item} />
</ToggleButtonGroup>
</div>
@@ -183,10 +183,10 @@
>{/if}{/if}
</div>
<div class="flex flex-col gap-2">
<ToggleButtonGroup bind:selected={editorKind}>
<ToggleButton value="plain" label="Plain" />
<ToggleButton value="json" label="Json" />
<ToggleButton value="yaml" label="YAML" />
<ToggleButtonGroup bind:selected={editorKind} let:item>
<ToggleButton value="plain" label="Plain" {item} />
<ToggleButton value="json" label="Json" {item} />
<ToggleButton value="yaml" label="YAML" {item} />
</ToggleButtonGroup>
{#if editorKind == 'plain'}
<textarea
+12 -3
View File
@@ -263,13 +263,21 @@
}
}}
class="mb-4"
let:item
>
<ToggleButton position="left" value="normal" size="sm" label="Any jobs within worker tags" />
<ToggleButton
position="left"
value="normal"
size="sm"
label="Any jobs within worker tags"
{item}
/>
<ToggleButton
position="dedicated"
value="dedicated"
size="sm"
label="Dedicated to a script/flow"
{item}
/>
</ToggleButtonGroup>
{#if selected == 'normal'}
@@ -682,9 +690,10 @@
envvar.value = undefined
}
}}
let:item
>
<ToggleButton position="left" value="dynamic" label="Dynamic" />
<ToggleButton position="right" value="static" label="Static" />
<ToggleButton value="dynamic" label="Dynamic" {item} />
<ToggleButton value="static" label="Static" {item} />
</ToggleButtonGroup>
<input
type="text"
@@ -1553,18 +1553,21 @@
on:selected={({ detail }) => {
$app.fullscreen = detail === 'true'
}}
let:item
>
<ToggleButton
icon={AlignHorizontalSpaceAround}
value={'false'}
tooltip="The max width is 1168px and the content stay centered instead of taking the full page width"
iconProps={{ size: 16 }}
{item}
/>
<ToggleButton
tooltip="The width is of the app if the full width of its container"
icon={Expand}
value={'true'}
iconProps={{ size: 16 }}
{item}
/>
</ToggleButtonGroup>
{/if}
@@ -1578,40 +1581,46 @@
setTheme(theme)
}}
selected={$app.darkMode === undefined ? 'auto' : $app.darkMode ? 'dark' : 'sun'}
let:item
>
<ToggleButton
icon={SunMoon}
value={'auto'}
tooltip="The app mode between dark/light is automatic"
iconProps={{ size: 16 }}
{item}
/>
<ToggleButton
icon={Sun}
value={'sun'}
tooltip="Force light mode"
iconProps={{ size: 16 }}
{item}
/>
<ToggleButton
tooltip="Force dark mode"
icon={Moon}
value={'dark'}
iconProps={{ size: 16 }}
{item}
/>
</ToggleButtonGroup>
{/if}
<div class="flex flex-row gap-2">
<ToggleButtonGroup class="h-[30px]" bind:selected={$breakpoint}>
<ToggleButtonGroup class="h-[30px]" bind:selected={$breakpoint} let:item>
<ToggleButton
tooltip="Computer View"
icon={Laptop2}
value={'lg'}
iconProps={{ size: 16 }}
{item}
/>
<ToggleButton
tooltip="Mobile View"
icon={Smartphone}
value={'sm'}
iconProps={{ size: 16 }}
{item}
/>
{#if $breakpoint === 'sm'}
<Toggle
@@ -18,6 +18,7 @@
jobsById.set({})
jobs.set([])
}}
let:item
>
<ToggleButton
label="Editor"
@@ -26,6 +27,7 @@
disabled={loading}
iconProps={{ size: 16 }}
class="gap-2"
{item}
/>
<ToggleButton
label="Preview"
@@ -36,5 +38,6 @@
id="app-editor-preview-toggle"
iconProps={{ size: 16 }}
class="gap-2"
{item}
/>
</ToggleButtonGroup>
@@ -124,27 +124,31 @@
{/if}
{#if quickStyleProperties?.length}
<ToggleButtonGroup
bind:selected={richEditorOpen}
on:selected={() => {
selected={richEditorOpen ? 'true' : 'false'}
on:selected={({ detail }) => {
richEditorOpen = detail === 'true'
if (richEditorOpen !== isQuickMenuOpen) {
toggleQuickMenu()
richEditorOpen = isQuickMenuOpen
}
}}
let:item
>
<ToggleButton
small
light
value={false}
value={'false'}
icon={Code}
tooltip="Edit the CSS directly"
{item}
/>
<ToggleButton
small
light
value={true}
value={'true'}
icon={Paintbrush2}
tooltip="Open the rich editor to style the component with a visual interface"
{item}
/>
</ToggleButtonGroup>
{/if}
@@ -26,10 +26,11 @@
noWFull
on:selected={() => ($app = $app)}
bind:selected={component.horizontalAlignment}
let:item
>
<ToggleButton value="left" icon={AlignStartVertical} />
<ToggleButton value="center" icon={AlignCenterVertical} />
<ToggleButton value="right" icon={AlignEndVertical} />
<ToggleButton value="left" icon={AlignStartVertical} {item} />
<ToggleButton value="center" icon={AlignCenterVertical} {item} />
<ToggleButton value="right" icon={AlignEndVertical} {item} />
</ToggleButtonGroup>
{/if}
{#if component.type !== 'formcomponent' && component.verticalAlignment}
@@ -37,10 +38,11 @@
noWFull
on:selected={() => ($app = $app)}
bind:selected={component.verticalAlignment}
let:item
>
<ToggleButton value="top" icon={AlignStartHorizontal} />
<ToggleButton value="center" icon={AlignCenterHorizontal} />
<ToggleButton value="bottom" icon={AlignEndHorizontal} />
<ToggleButton value="top" icon={AlignStartHorizontal} {item} />
<ToggleButton value="center" icon={AlignCenterHorizontal} {item} />
<ToggleButton value="bottom" icon={AlignEndHorizontal} {item} />
</ToggleButtonGroup>
{/if}
</div>
@@ -56,6 +56,7 @@
}}
noWFull
bind:selected={componentInput.type}
let:item
>
{#if componentInput.fieldType === 'template'}
{#if componentInput.type == 'template'}
@@ -65,6 +66,7 @@
disabled={disableStatic}
icon={CurlyBraces}
label="Template"
{item}
/>
{:else}
<ToggleButton
@@ -73,6 +75,7 @@
disabled={disableStatic}
icon={CurlyBraces}
label="Template"
{item}
/>
{/if}
{:else if componentInput.noStatic !== true}
@@ -82,6 +85,7 @@
disabled={disableStatic}
iconOnly={clientWidth < iconOnlyThreshold}
icon={Pen}
{item}
/>
{/if}
{#if componentInput.type == 'connected'}
@@ -90,6 +94,7 @@
icon={Plug2}
iconOnly={clientWidth < iconOnlyThreshold}
label="Connect"
{item}
/>
{/if}
<ToggleButton
@@ -97,6 +102,7 @@
icon={FunctionSquare}
iconOnly={clientWidth < iconOnlyThreshold}
label="Eval"
{item}
/>
<ToggleButton
@@ -104,6 +110,7 @@
icon={Code}
iconOnly={clientWidth < iconOnlyThreshold}
label="Compute"
{item}
id="data-source-compute"
/>
</ToggleButtonGroup>
@@ -394,19 +394,22 @@
<div class="p-2">
<ToggleButtonGroup
bind:selected
on:selected={() => {
handleSelected(selected)
on:selected={({ detail }) => {
handleSelected(detail)
}}
let:item
>
<ToggleButton
value="ui-editor"
label="UI Editor"
tooltip="Use the UI editor to quickly create a plotly chart."
{item}
/>
<ToggleButton
value="json"
label="JSON"
tooltip="Switch to JSON mode for complete customization of Plotly settings."
{item}
/>
</ToggleButtonGroup>
</div>
@@ -141,21 +141,28 @@
componentInput['expr'] = JSON.stringify(JSON.parse(componentInput['expr']), null, 4)
}
}}
let:item
>
<ToggleButton value="static" icon={Pen} iconOnly tooltip="Static" />
<ToggleButton value="static" icon={Pen} iconOnly tooltip="Static" {item} />
{#if userInputEnabled}
<ToggleButton value="user" icon={User} iconOnly tooltip="User Input" />
<ToggleButton value="user" icon={User} iconOnly tooltip="User Input" {item} />
{/if}
{#if fileUpload}
<ToggleButton value="upload" icon={Upload} iconOnly tooltip="Upload" />
<ToggleButton value="upload" icon={Upload} iconOnly tooltip="Upload" {item} />
{/if}
{#if componentInput?.type === 'connected'}
<ToggleButton value="connected" icon={Plug2} iconOnly tooltip="Connect" />
<ToggleButton value="connected" icon={Plug2} iconOnly tooltip="Connect" {item} />
{/if}
{#if componentInput?.type === 'eval'}
<ToggleButton value="eval" icon={FunctionSquare} iconOnly tooltip="Eval Legacy" />
<ToggleButton
value="eval"
icon={FunctionSquare}
iconOnly
tooltip="Eval Legacy"
{item}
/>
{/if}
<ToggleButton value="evalv2" icon={FunctionSquare} iconOnly tooltip="Eval" />
<ToggleButton value="evalv2" icon={FunctionSquare} iconOnly tooltip="Eval" {item} />
</ToggleButtonGroup>
<ConnectionButton {closeConnection} {openConnection} isOpen={!!$connectingInput.opened} />
{/if}
@@ -289,21 +289,30 @@
{#if $workspaceStore == 'admins'}
<div class="flex gap-1 relative w-full">
<span class="text-xs absolute -top-4">Scope</span>
<ToggleButtonGroup bind:selected={scope}>
<ToggleButtonGroup
selected={scope ?? 'admins'}
on:selected={({ detail }) => {
scope = detail === 'admins' ? undefined : detail
}}
let:item
>
<ToggleButton
value={undefined}
value={'admins'}
label="Admins"
tooltip="Displays events from the admins workspace only."
{item}
/>
<ToggleButton
value="all_workspaces"
label="All"
tooltip="Displays events from all workspaces."
{item}
/>
<ToggleButton
value="instance"
label="Instance"
tooltip="Displays instance-scope events, such as user logins and registrations, instance user and group management, and worker configuration changes."
{item}
/>
</ToggleButtonGroup>
</div>
@@ -1,13 +1,9 @@
<script lang="ts">
import { getContext } from 'svelte'
import { Tab } from '@rgossiaux/svelte-headlessui'
import type { ToggleButtonContext } from './ToggleButtonGroup.svelte'
import { twMerge } from 'tailwind-merge'
import Popover from '$lib/components/Popover.svelte'
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
import { type ToggleGroupElements, type ToggleGroupItemProps, melt } from '@melt-ui/svelte'
import { Info } from 'lucide-svelte'
export let value: any
export let label: string | undefined = undefined
export let iconOnly: boolean = false
export let tooltip: string | undefined = undefined
@@ -20,48 +16,51 @@
export let showTooltipIcon: boolean = false
export let documentationLink: string | undefined = undefined
export let id: string | undefined = undefined
const { select, selected } = getContext<ToggleButtonContext>('ToggleButtonGroup')
export let item: ToggleGroupElements['item']
export let value: ToggleGroupItemProps
</script>
<Popover
notClickable
<Tooltip
class={twMerge('flex', disabled ? 'cursor-not-allowed' : 'cursor-pointer')}
disablePopup={tooltip === undefined}
disappearTimeout={0}
{documentationLink}
>
<div {id} class="flex">
<Tab
{disabled}
class={twMerge(
' rounded-md transition-all text-xs flex gap-1 flex-row items-center',
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
light ? 'font-medium' : '',
$selected === value
? 'bg-surface shadow-md'
: 'bg-surface-secondary hover:bg-surface-hover',
$$props.class
)}
on:click={() => select(value)}
>
{#if icon}
<svelte:component
this={icon}
size={small ? 12 : 14}
color={$selected === value ? selectedColor : '#9CA3AF'}
{...iconProps}
/>
{/if}
{#if label && !iconOnly}
{label}
{/if}
{#if showTooltipIcon}
<Info size={14} class="text-gray-400" />
{/if}
</Tab>
</div>
<button
{id}
{disabled}
class={twMerge(
'group rounded-md transition-all text-xs flex gap-1 flex-row items-center',
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
light ? 'font-medium' : '',
'data-[state=on]:bg-surface data-[state=on]:shadow-md',
'bg-surface-secondary hover:bg-surface-hover',
$$props.class
)}
use:melt={$item(value)}
style={`--selected-color: ${selectedColor}`}
>
{#if icon}
<svelte:component
this={icon}
size={small ? 12 : 14}
{...iconProps}
class={twMerge(
'text-gray-400',
'group-data-[state=on]:text-[var(--selected-color)]',
iconProps.class
)}
/>
{/if}
{#if label && !iconOnly}
{label}
{/if}
{#if showTooltipIcon}
<Info size={14} class="text-gray-400" />
{/if}
</button>
<svelte:fragment slot="text">
{tooltip}
</svelte:fragment>
</Popover>
</Tooltip>
@@ -1,53 +1,53 @@
<script context="module" lang="ts">
export type ToggleButtonContext = {
selected: Writable<any>
select: (value: any) => void
}
</script>
<script lang="ts">
import { createEventDispatcher, setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
import { TabGroup, TabList } from '@rgossiaux/svelte-headlessui'
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { createSync } from '@melt-ui/svelte'
export let id: string | null | undefined = undefined
export let selected: any
export let selected: string | string[] | undefined = undefined
export let noWFull: boolean = false
export let disabled: boolean = false
export let tabListClass: string = ''
export let allowEmpty: boolean = false
const dispatch = createEventDispatcher()
const selectedContent = writable(selected)
function setSelected(selected: any) {
if ($selectedContent != selected) {
selectedContent.set(selected)
}
}
import { createToggleGroup, melt } from '@melt-ui/svelte'
$: setSelected(selected)
setContext<ToggleButtonContext>('ToggleButtonGroup', {
selected: selectedContent,
select: (value: any) => {
selectedContent.set(value)
selected = value
dispatch('selected', value)
const {
elements: { root, item },
options: { disabled: disabledOption },
states
} = createToggleGroup({
type: 'single',
onValueChange: ({ curr, next }) => {
if (next === undefined && !allowEmpty) {
return curr
}
if (curr !== next) {
dispatch('selected', next)
}
return next
}
})
$: $disabledOption = disabled
const sync = createSync(states)
$: sync.value(selected, (v) => (selected = v))
</script>
<TabGroup
{id}
<div
use:melt={$root}
class={twMerge(
`h-8 flex ${noWFull ? '' : 'w-full'} ${disabled ? 'disabled' : ''}`,
$$props.class
$$props.class,
'flex items-center data-[orientation="vertical"]:flex-col'
)}
aria-label="Toggle button group"
{id}
>
<TabList
class={twMerge('flex bg-surface-secondary rounded-md p-0.5 gap-1 h-full ', tabListClass)}
>
<slot />
</TabList>
</TabGroup>
<div class={twMerge('flex bg-surface-secondary rounded-md p-0.5 gap-1 h-full ', tabListClass)}>
<slot {item} />
</div>
</div>
@@ -1,16 +1,15 @@
<script lang="ts">
import { getContext } from 'svelte'
import { Tab } from '@rgossiaux/svelte-headlessui'
import type { ToggleButtonContext } from './ToggleButtonGroup.svelte'
import { twMerge } from 'tailwind-merge'
import Popover from '$lib/components/Popover.svelte'
import DropdownV2 from '$lib/components/DropdownV2.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
export let disabled: boolean = false
export let small = false
export let light = false
export let id: string | undefined = undefined
export let item: any | undefined = undefined
export let selected: string | undefined = undefined
type TogglableItem = {
label: string
value: string
@@ -18,11 +17,14 @@
export let togglableItems: TogglableItem[]
const { select, selected } = getContext<ToggleButtonContext>('ToggleButtonGroup')
function select(v: string) {
console.log('dbg select', v)
selected = v
}
let items = togglableItems.map((i) => ({ displayName: i.label, action: () => select(i.value) }))
function isAnOptionSelected(selected: string) {
function isAnOptionSelected(selected: string | undefined) {
return togglableItems.some((i) => i.value === selected)
}
</script>
@@ -34,21 +36,16 @@
disappearTimeout={0}
>
<div {id} class="flex">
{#if isAnOptionSelected($selected)}
<Tab
{#if isAnOptionSelected(selected)}
<ToggleButton
{disabled}
class={twMerge(
' rounded-md transition-all text-xs flex gap-1 flex-row items-center',
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
light ? 'font-medium' : '',
isAnOptionSelected($selected)
? 'bg-surface shadow-md'
: 'bg-surface-secondary hover:bg-surface-hover',
$$props.class
)}
>
{togglableItems.find((i) => i.value === $selected)?.label}
</Tab>
value={selected ?? ''}
{item}
{small}
{light}
{id}
label={togglableItems.find((i) => i.value === selected)?.label}
/>
{/if}
<div class="flex items-center">
<DropdownV2 {items} />
@@ -1,37 +0,0 @@
<script lang="ts">
import { Tab } from '@rgossiaux/svelte-headlessui'
import { twMerge } from 'tailwind-merge'
import Toggle from '$lib/components/Toggle.svelte'
export let label: string | undefined = undefined
export let tooltip: string | undefined = undefined
export let disabled: boolean = false
export let small = false
export let light = false
export let checked: boolean = false
export let id: string | undefined = undefined
</script>
<div {id} class="flex">
<Tab
{disabled}
class={twMerge(
' rounded-md transition-all text-xs flex gap-1 flex-row items-center',
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
light ? 'font-medium' : '',
'bg-surface-secondary ',
$$props.class
)}
>
<Toggle
size="xs"
options={{
right: label,
rightTooltip: tooltip
}}
textClass="text-2xs whitespace-nowrap white !w-full"
bind:checked
/>
</Tab>
</div>
@@ -40,9 +40,10 @@
$currentStepStore = numberToChars(0)
}
}}
let:item
>
<ToggleButton value="sequence" label="Sequence" />
<ToggleButton value="trigger" label="Trigger" />
<ToggleButton value="sequence" label="Sequence" {item} />
<ToggleButton value="trigger" label="Trigger" {item} />
</ToggleButtonGroup>
{#if flowCopilotMode === 'trigger'}
<Alert title="Trigger flow builder">
@@ -406,9 +406,15 @@
{:else if $copilotInfo.exists_ai_resource}
<div class="flex flex-col gap-4">
<div class="flex flex-row justify-between items-center w-96 gap-2">
<ToggleButtonGroup class="w-auto shrink-0 h-auto" bind:selected={mode}>
<ToggleButton value={'gen'} label="Generate from scratch" light class="px-2" />
<ToggleButton value={'edit'} label="Edit existing code" light class="px-2" />
<ToggleButtonGroup class="w-auto shrink-0 h-auto" bind:selected={mode} let:item>
<ToggleButton
value={'gen'}
label="Generate from scratch"
light
class="px-2"
{item}
/>
<ToggleButton value={'edit'} label="Edit existing code" light class="px-2" {item} />
</ToggleButtonGroup>
<div class="min-w-0">
@@ -520,14 +526,16 @@
class="w-auto shrink-0"
selected={dbSchema?.publicOnly ? 'true' : 'false'}
on:selected={handlePublicOnlySelected}
let:item
>
<ToggleButton
value={'true'}
label={(dbSchema.schema?.dbo ? 'Dbo' : 'Public') + ' schema'}
small
light
{item}
/>
<ToggleButton value={'false'} label="All schemas" small light />
<ToggleButton value={'false'} label="All schemas" small light {item} />
</ToggleButtonGroup>
{/if}
</div>
@@ -111,10 +111,10 @@
<slot name="postgres" />
{:else if triggerSelected === 'kafka' || triggerSelected === 'nats' || triggerSelected === 'sqs'}
<div class="m-1.5">
<ToggleButtonGroup bind:selected={eventStreamType}>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} />
<ToggleButtonGroup bind:selected={eventStreamType} let:item>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} {item} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} {item} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} {item} />
</ToggleButtonGroup>
</div>
{#if eventStreamType === 'kafka'}
@@ -110,9 +110,9 @@
<div class="flex flex-col gap-2">
<div class="flex flex-row justify-between">
<div class="text-xs font-semibold flex flex-row items-center">Call method</div>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={requestType}>
<ToggleButton label="By path" value="path" />
<ToggleButton label="By hash" value="hash" />
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={requestType} let:item>
<ToggleButton label="By path" value="path" {item} />
<ToggleButton label="By hash" value="hash" {item} />
</ToggleButtonGroup>
</div>
</div>
@@ -68,12 +68,13 @@
{:else}{#if !failureModule && !preprocessorModule}
<div class="center-center">
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButtonGroup bind:selected={kind} let:item>
<ToggleButton
value="script"
icon={Code}
label="Action"
tooltip="An action script is simply a script that is neither a trigger nor an approval script. Those are the majority of the scripts."
{item}
/>
{#if !shouldDisableTriggerScripts}
<ToggleButton
@@ -81,6 +82,7 @@
icon={Zap}
label="Trigger"
tooltip="Used as a first step most commonly with a state and a schedule to watch for changes on an external system, compute the diff since last time and set the new state. The diffs are then treated one by one with a for-loop."
{item}
/>
{/if}
<ToggleButton
@@ -88,6 +90,7 @@
icon={Check}
label="Approval"
tooltip="An approval step will suspend the execution of a flow until it has been approved through the resume endpoints or the approval page by and solely by the recipients of those secret urls."
{item}
/>
</ToggleButtonGroup>
</div>
@@ -629,13 +629,18 @@
</div>
<div class="flex gap-2 justify-between mb-4 items-center">
<div class="flex gap-2">
<ToggleButtonGroup bind:selected={s3Kind} class="w-auto">
<ToggleButtonGroup bind:selected={s3Kind} class="w-auto" let:item>
{#if flowModule.value['language'] === 'deno'}
<ToggleButton value="s3_client" size="sm" label="S3 lite client" />
<ToggleButton
value="s3_client"
size="sm"
label="S3 lite client"
{item}
/>
{:else}
<ToggleButton value="s3_client" size="sm" label="Boto3" />
<ToggleButton value="polars" size="sm" label="Polars" />
<ToggleButton value="duckdb" size="sm" label="DuckDB" />
<ToggleButton value="s3_client" size="sm" label="Boto3" {item} />
<ToggleButton value="polars" size="sm" label="Polars" {item} />
<ToggleButton value="duckdb" size="sm" label="DuckDB" {item} />
{/if}
</ToggleButtonGroup>
</div>
@@ -68,10 +68,11 @@
setExponentialRetries()
}
}}
let:item
>
<ToggleButton light value="disabled" label="Disabled" />
<ToggleButton light value="constant" label="Constant" />
<ToggleButton light value="exponential" label="Exponential" />
<ToggleButton light value="disabled" label="Disabled" {item} />
<ToggleButton light value="constant" label="Constant" {item} />
<ToggleButton light value="exponential" label="Exponential" {item} />
</ToggleButtonGroup>
<div class="flex h-[calc(100%-22px)]">
<div class="w-1/2 h-full overflow-auto pr-2">
@@ -1,7 +1,10 @@
<script lang="ts">
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import { twMerge } from 'tailwind-merge'
export let color: string = 'black'
</script>
<BarsStaggered size={14} style={`color: ${color};`} />
<div class={twMerge(`text-${color}`, $$props.class)}>
<BarsStaggered size={14} />
</div>
@@ -346,9 +346,16 @@
setQuery($page.url, 'kind', itemKind)
}}
class="h-10"
let:item
>
<ToggleButton value="all" label="All" class="text-sm px-4 py-2" />
<ToggleButton value="script" icon={Code2} label="Scripts" class="text-sm px-4 py-2" />
<ToggleButton value="all" label="All" class="text-sm px-4 py-2" {item} />
<ToggleButton
value="script"
icon={Code2}
label="Scripts"
class="text-sm px-4 py-2"
{item}
/>
{#if HOME_SEARCH_SHOW_FLOW}
<ToggleButton
value="flow"
@@ -356,6 +363,7 @@
icon={FlowIcon}
class="text-sm px-4 py-2"
selectedColor="#14b8a6"
{item}
/>
{/if}
<ToggleButton
@@ -364,6 +372,7 @@
icon={LayoutDashboard}
class="text-sm px-4 py-2"
selectedColor="#fb923c"
{item}
/>
</ToggleButtonGroup>
</div>
+20 -20
View File
@@ -11,25 +11,25 @@ export interface Setting {
label: string
tooltip?: string
// If not specified, label will be used
value?: any
value?: string
}[]
fieldType:
| 'text'
| 'number'
| 'boolean'
| 'password'
| 'select'
| 'textarea'
| 'codearea'
| 'seconds'
| 'email'
| 'license_key'
| 'object_store_config'
| 'critical_error_channels'
| 'slack_connect'
| 'smtp_connect'
| 'indexer_rates'
| 'otel'
| 'text'
| 'number'
| 'boolean'
| 'password'
| 'select'
| 'textarea'
| 'codearea'
| 'seconds'
| 'email'
| 'license_key'
| 'object_store_config'
| 'critical_error_channels'
| 'slack_connect'
| 'smtp_connect'
| 'indexer_rates'
| 'otel'
storage: SettingStorage
advancedToggle?: {
label: string
@@ -81,9 +81,9 @@ export const settings: Record<string, Setting[]> = {
isValid: (value: string | undefined) =>
value
? value?.startsWith('http') &&
value.includes('://') &&
!value?.endsWith('/') &&
!value?.endsWith(' ')
value.includes('://') &&
!value?.endsWith('/') &&
!value?.endsWith(' ')
: false
},
{
@@ -54,7 +54,7 @@
}
})
let isOpen = false
export let isOpen = false
const sync = createSync(states)
$: sync.open(isOpen, (v) => (isOpen = v))
@@ -103,6 +103,8 @@
let labelTimeout: NodeJS.Timeout | undefined = undefined
let concurrencyKeyTimeout: NodeJS.Timeout | undefined = undefined
let tagTimeout: NodeJS.Timeout | undefined = undefined
let allWorkspacesValue = allWorkspaces ? 'all' : 'admins'
</script>
<div class="flex gap-4">
@@ -111,9 +113,13 @@
{#if $workspaceStore == 'admins'}
<div class="relative">
<span class="text-xs absolute -top-4">Workspaces</span>
<ToggleButtonGroup bind:selected={allWorkspaces}>
<ToggleButton value={false} label="Admins" />
<ToggleButton value={true} label="All" />
<ToggleButtonGroup
bind:selected={allWorkspacesValue}
on:selected={({ detail }) => (allWorkspaces = detail === 'all')}
let:item
>
<ToggleButton value={'admins'} label="Admins" {item} />
<ToggleButton value={'all'} label="All" {item} />
</ToggleButtonGroup>
</div>
{/if}
@@ -135,10 +141,11 @@
autoSet = false
}
}}
let:item
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
<ToggleButton value="path" label="Path" {item} />
<ToggleButton value="user" label="User" {item} />
<ToggleButton value="folder" label="Folder" {item} />
<ToggleButtonMore
togglableItems={[
{ label: 'Schedule path', value: 'schedulePath' },
@@ -146,6 +153,8 @@
{ label: 'Label', value: 'label' },
{ label: 'Tag', value: 'tag' }
]}
{item}
bind:selected={filterBy}
/>
</ToggleButtonGroup>
</div>
@@ -410,48 +419,56 @@
</div>
<div class="relative">
<span class="text-xs absolute -top-4">Kind</span>
<ToggleButtonGroup bind:selected={jobKindsCat}>
<ToggleButton value="all" label="All" />
<ToggleButtonGroup bind:selected={jobKindsCat} let:item>
<ToggleButton value="all" label="All" {item} />
<ToggleButton
value="runs"
label="Runs"
showTooltipIcon
tooltip="Runs are jobs that have no parent jobs (flows are jobs that are parent of the jobs they start), they have been triggered through the UI, a schedule or webhook"
{item}
/>
<ToggleButton
value="previews"
label="Previews"
showTooltipIcon
tooltip="Previews are jobs that have been started in the editor as 'Tests'"
{item}
/>
<ToggleButton
value="dependencies"
label="Deps"
showTooltipIcon
tooltip="Deploying a script, flow or an app launch a dependency job that create and then attach the lockfile to the deployed item. This mechanism ensure that logic is always executed with the exact same direct and indirect dependencies."
{item}
/>
<ToggleButton
value="deploymentcallbacks"
label="Sync"
showTooltipIcon
tooltip="Sync jobs that are triggered on every script deployment to sync the workspace with the Git repository configured in the the workspace settings"
{item}
/>
</ToggleButtonGroup>
</div>
<div class="relative">
<span class="text-xs absolute -top-4">Status</span>
<ToggleButtonGroup
allowEmpty
bind:selected={success}
on:selected={() => dispatch('successChange', success)}
selected={success ?? 'all'}
on:selected={({ detail }) => {
success = detail === 'all' ? undefined : detail
dispatch('successChange', success)
}}
let:item
>
<ToggleButton value={undefined} label="All" />
<ToggleButton value={'all'} label="All" {item} />
<ToggleButton
value={'running'}
tooltip="Running"
class="whitespace-nowrap"
icon={PlayCircle}
iconProps={{ color: success === 'running' ? 'blue' : 'gray' }}
{item}
/>
<ToggleButton
value={'success'}
@@ -459,6 +476,7 @@
class="whitespace-nowrap"
icon={CheckCircle2}
iconProps={{ color: success === 'success' ? 'green' : 'gray' }}
{item}
/>
<ToggleButton
value={'failure'}
@@ -466,6 +484,7 @@
class="whitespace-nowrap"
icon={AlertCircle}
iconProps={{ color: success === 'failure' ? 'red' : 'gray' }}
{item}
/>
{#if success == 'waiting'}
<ToggleButton
@@ -474,6 +493,7 @@
class="whitespace-nowrap"
icon={Hourglass}
iconProps={{ color: 'blue' }}
{item}
/>
{:else if success == 'suspended'}
<ToggleButton
@@ -482,6 +502,7 @@
class="whitespace-nowrap"
icon={Hourglass}
iconProps={{ color: 'blue' }}
{item}
/>
{/if}
</ToggleButtonGroup>
@@ -505,6 +526,7 @@
{#if mobile || true}
<Label label="Filter by">
<ToggleButtonGroup
let:item
bind:selected={filterBy}
on:selected={() => {
if (!autoSet) {
@@ -520,13 +542,13 @@
}
}}
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
<ToggleButton value="schedulePath" label="Schedule" />
<ToggleButton value="concurrencyKey" label="Concurrency" />
<ToggleButton value="tag" label="Tag" />
<ToggleButton value="label" label="Label" />
<ToggleButton value="path" label="Path" {item} />
<ToggleButton value="user" label="User" {item} />
<ToggleButton value="folder" label="Folder" {item} />
<ToggleButton value="schedulePath" label="Schedule" {item} />
<ToggleButton value="concurrencyKey" label="Concurrency" {item} />
<ToggleButton value="tag" label="Tag" {item} />
<ToggleButton value="label" label="Label" {item} />
</ToggleButtonGroup>
</Label>
@@ -726,41 +748,49 @@
{/if}
<Label label="Kind">
<ToggleButtonGroup bind:selected={jobKindsCat}>
<ToggleButton value="all" label="All" />
<ToggleButtonGroup bind:selected={jobKindsCat} let:item>
<ToggleButton value="all" label="All" {item} />
<ToggleButton
value="runs"
label="Runs"
showTooltipIcon
tooltip="Runs are jobs that have no parent jobs (flows are jobs that are parent of the jobs they start), they have been triggered through the UI, a schedule or webhook"
{item}
/>
<ToggleButton
value="previews"
label="Previews"
showTooltipIcon
tooltip="Previews are jobs that have been started in the editor as 'Tests'"
{item}
/>
<ToggleButton
value="dependencies"
label="Deps"
showTooltipIcon
tooltip="Deploying a script, flow or an app launch a dependency job that create and then attach the lockfile to the deployed item. This mechanism ensure that logic is always executed with the exact same direct and indirect dependencies."
{item}
/>
<ToggleButton
value="deploymentcallbacks"
label="Sync"
showTooltipIcon
tooltip="Sync jobs that are triggered on every script deployment to sync the workspace with the Git repository configured in the the workspace settings"
{item}
/>
</ToggleButtonGroup>
</Label>
<Label label="Status">
<ToggleButtonGroup bind:selected={success}>
<ToggleButton value={undefined} label="All" />
<ToggleButton value={'running'} label="Running" class="whitespace-nowrap" />
<ToggleButton value={'success'} label="Success" class="whitespace-nowrap" />
<ToggleButton value={'failure'} label="Failure" class="whitespace-nowrap" />
<ToggleButtonGroup
selected={success ?? 'all'}
on:selected={({ detail }) => (success = detail === 'all' ? undefined : detail)}
let:item
>
<ToggleButton value={'all'} label="All" {item} />
<ToggleButton value={'running'} label="Running" class="whitespace-nowrap" {item} />
<ToggleButton value={'success'} label="Success" class="whitespace-nowrap" {item} />
<ToggleButton value={'failure'} label="Failure" class="whitespace-nowrap" {item} />
</ToggleButtonGroup>
</Label>
{/if}
@@ -181,9 +181,14 @@
<div class="flex flex-col gap-2">
{#if type === 'object' && schema.oneOf && schema.oneOf.length >= 2}
<div class="flex flex-row gap-1 items-center justify-start">
<ToggleButtonGroup bind:selected={oneOfSelected} class="w-auto" tabListClass="flex-wrap">
<ToggleButtonGroup
bind:selected={oneOfSelected}
class="h-auto w-auto"
tabListClass="flex-wrap"
let:item
>
{#each schema.oneOf as obj}
<ToggleButton value={obj.title} label={obj.title} />
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
{/each}
</ToggleButtonGroup>
@@ -193,9 +193,9 @@
bind:currencyLocale={extra['currencyLocale']}
/>
{:else if type == 'object' && oneOf && oneOf.length >= 2 && !isFlowInput && !isAppInput}
<ToggleButtonGroup bind:selected={oneOfSelected} class="mb-2">
<ToggleButtonGroup bind:selected={oneOfSelected} class="mb-2" let:item>
{#each oneOf as obj}
<ToggleButton value={obj.title} label={obj.title} />
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
{/each}
</ToggleButtonGroup>
{#if oneOfSelected && oneOfSchemas}
@@ -71,9 +71,9 @@
$: enableAllState = (() => {
const values = Object.values(operatorWorkspaceSettings)
if (values.every((v) => v === true)) return true
if (values.every((v) => v === false)) return false
return null
if (values.every((v) => v === true)) return 'true'
if (values.every((v) => v === false)) return 'false'
return undefined
})()
function toggleAllSettings(event) {
@@ -111,9 +111,25 @@
<Cell head first>Section</Cell>
<Cell head>Description</Cell>
<Cell head last>
<ToggleButtonGroup bind:selected={enableAllState} on:selected={toggleAllSettings}>
<ToggleButton icon={EyeIcon} small={true} value={true} label="Enable All" />
<ToggleButton icon={EyeOffIcon} small={true} value={false} label="Disable All" />
<ToggleButtonGroup
bind:selected={enableAllState}
on:selected={toggleAllSettings}
let:item
>
<ToggleButton
icon={EyeIcon}
small={true}
value={'true'}
label="Enable All"
{item}
/>
<ToggleButton
icon={EyeOffIcon}
small={true}
value={'false'}
label="Disable All"
{item}
/>
</ToggleButtonGroup>
</Cell>
</tr>
@@ -124,9 +140,13 @@
<Cell first>{title}</Cell>
<Cell>{description}</Cell>
<Cell last class="pl-8">
<ToggleButtonGroup bind:selected={operatorWorkspaceSettings[key]}>
<ToggleButton icon={EyeIcon} small={true} value={true} label="On" />
<ToggleButton icon={EyeOffIcon} small={true} value={false} label="Off" />
<ToggleButtonGroup
selected={operatorWorkspaceSettings[key] ? 'on' : 'off'}
on:selected={({ detail }) => (operatorWorkspaceSettings[key] = detail === 'on')}
let:item
>
<ToggleButton icon={EyeIcon} small={true} value={'on'} label="On" {item} />
<ToggleButton icon={EyeOffIcon} small={true} value={'off'} label="Off" {item} />
</ToggleButtonGroup>
</Cell>
</tr>
@@ -205,9 +205,10 @@
autoAdd = e.detail === 'add'
}
}}
let:item
>
<ToggleButton value="invite" size="xs" label="Auto-invite" />
<ToggleButton value="add" size="xs" label="Auto-add" />
<ToggleButton value="invite" size="xs" label="Auto-invite" {item} />
<ToggleButton value="add" size="xs" label="Auto-add" {item} />
</ToggleButtonGroup>
{/if}
@@ -235,18 +236,19 @@
operatorOnly = e.detail === 'operator'
}
}}
let:item
>
<ToggleButton
value="operator"
size="xs"
label="Operator"
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
{item}
/>
<ToggleButton
value="developer"
size="xs"
label="Developer"
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
{item}
/>
</ToggleButtonGroup>
<div class="pt-2">
@@ -365,26 +367,27 @@
})
listUsers()
}}
let:item
>
<ToggleButton
value="operator"
size="xs"
label="Operator"
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
{item}
/>
<ToggleButton
value="developer"
size="xs"
label="Developer"
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
{item}
/>
<ToggleButton
value="admin"
size="xs"
label="Admin"
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
{item}
/>
</ToggleButtonGroup>
</div>
@@ -494,26 +497,27 @@
})
listUsers()
}}
let:item
>
<ToggleButton
value="operator"
size="xs"
label="Operator"
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
{item}
/>
<ToggleButton
value="developer"
size="xs"
label="Developer"
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
{item}
/>
<ToggleButton
value="admin"
size="xs"
label="Admin"
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
{item}
/>
</ToggleButtonGroup>
</div>
@@ -1,5 +1,4 @@
<script lang="ts">
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { Button } from '$lib/components/common'
import { Webhook, Route, Unplug, Mail, Plus, Database } from 'lucide-svelte'
import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte'
@@ -10,11 +9,10 @@
import CaptureIcon from './CaptureIcon.svelte'
import NatsIcon from '../icons/NatsIcon.svelte'
import AwsIcon from '../icons/AwsIcon.svelte'
import DropdownV2 from '$lib/components/DropdownV2.svelte'
export let small = false
let isOpen = false
const dispatch = createEventDispatcher()
function handleClick(kind: CaptureTriggerKind) {
@@ -22,12 +20,56 @@
kind: captureTriggerKindToTriggerKind(kind),
config: {}
})
isOpen = false
}
$: items = [
{
icon: Webhook,
displayName: 'Webhook',
action: () => handleClick('webhook')
},
{
icon: Route,
displayName: 'HTTP',
action: () => handleClick('http')
},
{
icon: Unplug,
displayName: 'Websocket',
action: () => handleClick('websocket')
},
{
icon: AwsIcon,
displayName: 'SQS',
action: () => handleClick('sqs')
},
{
icon: Database,
displayName: 'Postgres',
action: () => handleClick('postgres')
},
{
icon: Mail,
displayName: 'Email',
action: () => handleClick('email')
},
{
icon: KafkaIcon,
displayName: 'Kafka',
action: () => handleClick('kafka'),
disabled: !$enterpriseLicense
},
{
icon: NatsIcon,
displayName: 'Nats',
action: () => handleClick('nats'),
disabled: !$enterpriseLicense
}
]
</script>
<Popover closeButton={false} bind:isOpen>
<svelte:fragment slot="trigger">
<DropdownV2 {items} placement="bottom-start">
<svelte:fragment slot="buttonReplacement">
{#if small}
<Button
color="light"
@@ -54,82 +96,4 @@
</Button>
{/if}
</svelte:fragment>
<svelte:fragment slot="content">
<div class="flex flex-col bg-surface">
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('webhook')}
>
<div class="flex flex-row items-center gap-2">
<Webhook size={16} />
<p class="text-xs text-secondary">Webhook</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('http')}
>
<div class="flex flex-row items-center gap-2">
<Route size={16} />
<p class="text-xs text-secondary">HTTP</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('websocket')}
>
<div class="flex flex-row items-center gap-2">
<Unplug size={16} />
<p class="text-xs text-secondary">Websocket</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('sqs')}
>
<div class="flex flex-row items-center gap-2">
<AwsIcon size={16} />
<p class="text-xs text-secondary">SQS</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('postgres')}
>
<div class="flex flex-row items-center gap-2">
<Database size={16} />
<p class="text-xs text-secondary">Postgres</p>
</div>
</button>
<button
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('email')}
>
<div class="flex flex-row items-center gap-2">
<Mail size={16} />
<p class="text-xs text-secondary">Email</p>
</div>
</button>
<button
disabled={!$enterpriseLicense}
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('kafka')}
>
<div class="flex flex-row items-center gap-2">
<KafkaIcon size={16} />
<p class="text-xs text-secondary">Kafka</p>
</div>
</button>
<button
disabled={!$enterpriseLicense}
class="hover:bg-surface-hover p-2 transition-colors duration-150"
on:click={() => handleClick('nats')}
>
<div class="flex flex-row items-center gap-2">
<NatsIcon size={16} />
<p class="text-xs text-secondary">Nats</p>
</div>
</button>
</div>
</svelte:fragment>
</Popover>
</DropdownV2>
@@ -208,13 +208,15 @@
on:selected={(e) => {
initLoadCaptures(e.detail)
}}
let:item
>
<ToggleButton value="main" label={isFlow ? 'Flow' : 'Main'} small />
<ToggleButton value="main" label={isFlow ? 'Flow' : 'Main'} small {item} />
<ToggleButton
value="preprocessor"
label="Preprocessor"
small
tooltip="When the runnable has a preprocessor, it receives additional information about the request"
{item}
/>
</ToggleButtonGroup>
</div>
@@ -155,10 +155,10 @@
</div>
{:else if $selectedTrigger === 'kafka' || $selectedTrigger === 'nats' || $selectedTrigger === 'sqs'}
<div class="p-4 flex flex-col gap-2">
<ToggleButtonGroup bind:selected={eventStreamType}>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} />
<ToggleButtonGroup bind:selected={eventStreamType} let:item>
<ToggleButton value="kafka" label="Kafka" icon={KafkaIcon} {item} />
<ToggleButton value="nats" label="NATS" icon={NatsIcon} {item} />
<ToggleButton value="sqs" label="SQS" icon={AwsIcon} {item} />
</ToggleButtonGroup>
{#if eventStreamType === 'kafka'}
<KafkaTriggersPanel
@@ -150,12 +150,13 @@
disabled={!($userStore?.is_admin || $userStore?.is_super_admin) ||
!can_write ||
!!static_asset_config}
let:item
>
<ToggleButton label="GET" value="get" />
<ToggleButton label="POST" value="post" />
<ToggleButton label="PUT" value="put" />
<ToggleButton label="PATCH" value="patch" />
<ToggleButton label="DELETE" value="delete" />
<ToggleButton label="GET" value="get" {item} />
<ToggleButton label="POST" value="post" {item} />
<ToggleButton label="PUT" value="put" {item} />
<ToggleButton label="PATCH" value="patch" {item} />
<ToggleButton label="DELETE" value="delete" {item} />
</ToggleButtonGroup>
<div class="flex flex-col w-full mt-2">
<div class="flex justify-start w-full">
@@ -252,10 +252,11 @@
static_asset_config = undefined
}
}}
let:item
>
<ToggleButton label="Runnable" value="runnable" />
<ToggleButton label="Static asset" value="static_asset" />
<ToggleButton label="Static website" value="static_website" />
<ToggleButton label="Runnable" value="runnable" {item} />
<ToggleButton label="Static asset" value="static_asset" {item} />
<ToggleButton label="Static website" value="static_website" {item} />
</ToggleButtonGroup>
{/if}
@@ -385,18 +386,24 @@
<svelte:fragment slot="action">
<ToggleButtonGroup
class="w-auto h-full"
bind:selected={is_async}
selected={is_async ? 'async' : 'sync'}
on:selected={({ detail }) => {
is_async = detail === 'async'
}}
disabled={!can_write || !!static_asset_config}
let:item
>
<ToggleButton
label="Async"
value={true}
value="async"
tooltip="The returning value is the uuid of the job assigned to execute the job."
{item}
/>
<ToggleButton
label="Sync"
value={false}
value="sync"
tooltip="Triggers the execution, wait for the job to complete and return it as a response."
{item}
/>
</ToggleButtonGroup>
</svelte:fragment>
@@ -407,14 +414,19 @@
<svelte:fragment slot="action">
<ToggleButtonGroup
class="w-auto h-full"
bind:selected={requires_auth}
selected={requires_auth ? 'required' : 'none'}
on:selected={({ detail }) => {
requires_auth = detail === 'required'
}}
disabled={!can_write}
let:item
>
<ToggleButton label="None" value={false} />
<ToggleButton label="None" value="none" {item} />
<ToggleButton
label="Required"
value={true}
value="required"
tooltip="Requires authentication with read access on the route"
{item}
/>
</ToggleButtonGroup>
</svelte:fragment>
@@ -200,9 +200,10 @@
delete args.security
}
}}
let:item
>
<ToggleButton value="static" label="Static" small={true} />
<ToggleButton value="resource" label="Resource" icon={Boxes} small={true} />
<ToggleButton value="static" label="Static" small={true} {item} />
<ToggleButton value="resource" label="Resource" icon={Boxes} small={true} {item} />
</ToggleButtonGroup>
{/if}
</svelte:fragment>
@@ -251,9 +251,10 @@
delete args.require_tls
}
}}
let:item
>
<ToggleButton value="static" label="Static" small={true} />
<ToggleButton value="resource" label="Resource" icon={Boxes} small={true} />
<ToggleButton value="static" label="Static" small={true} {item} />
<ToggleButton value="resource" label="Resource" icon={Boxes} small={true} {item} />
</ToggleButtonGroup>
{/if}
</svelte:fragment>
@@ -490,9 +490,10 @@
replication_slot_name = ''
}}
disabled={!can_write}
let:item
>
<ToggleButton value="create" label="Create Slot" />
<ToggleButton value="get" label="Get Slot" />
<ToggleButton value="create" label="Create Slot" {item} />
<ToggleButton value="get" label="Get Slot" {item} />
</ToggleButtonGroup>
{#if selectedSlotAction === 'create'}
<div class="flex gap-3">
@@ -527,9 +528,10 @@
>
<div class="flex flex-col gap-3">
<ToggleButtonGroup
bind:selected={selectedPublicationAction}
selected={selectedPublicationAction}
disabled={!can_write}
on:selected={() => {
on:selected={({ detail }) => {
selectedPublicationAction = detail
if (selectedPublicationAction === 'create') {
selectedTable = 'specific'
publication_name = `windmill_publication_${random_adj()}`
@@ -541,9 +543,10 @@
relations = []
transaction_to_track = []
}}
let:item
>
<ToggleButton value="create" label="Create Publication" />
<ToggleButton value="get" label="Get Publication" />
<ToggleButton value="create" label="Create Publication" {item} />
<ToggleButton value="get" label="Get Publication" {item} />
</ToggleButtonGroup>
{#if selectedPublicationAction === 'create'}
<div class="flex gap-3">
@@ -17,6 +17,13 @@
let cached: Relations[] = relations
const defaultRelations: Relations[] = [
{
schema_name: 'public',
table_to_track: []
}
]
function addTable(name: string, index: number) {
if (relations == undefined || !Array.isArray(relations)) {
relations = []
@@ -36,18 +43,24 @@
<div class="grow-0 flex flex-row gap-2 w-full items-center">
<div class="grow-0">
<ToggleButtonGroup
on:selected={() => {
if (selectedTable === 'all') {
on:selected={({ detail }) => {
if (detail === 'all') {
cached = relations
relations = []
} else {
relations = cached
if (cached.length === 0) {
relations = defaultRelations
} else {
relations = cached
}
}
selectedTable = detail
}}
bind:selected={selectedTable}
selected={selectedTable}
let:item
>
<ToggleButton value="all" label="All Tables" />
<ToggleButton value="specific" label="Specific Tables" />
<ToggleButton value="all" label="All Tables" light {item} />
<ToggleButton value="specific" label="Specific Tables" light {item} />
</ToggleButtonGroup>
</div>
</div>
@@ -110,18 +110,20 @@
>
<div class="mt-2">
<ToggleButtonGroup
bind:selected={tab}
on:selected={() => {
if (tab === 'all') {
selected={tab}
on:selected={({ detail }) => {
if (detail === 'all') {
cached = message_attributes
message_attributes = ['All']
} else {
message_attributes = cached
}
tab = detail
}}
let:item
>
<ToggleButton value="all" label="All attributes" />
<ToggleButton value="specific" label="Specific attributes" />
<ToggleButton value="all" label="All attributes" {item} />
<ToggleButton value="specific" label="Specific attributes" {item} />
</ToggleButtonGroup>
</div>
<div class="flex flex-col mt-3 gap-1">
@@ -308,26 +308,29 @@ done`
<div class="flex flex-row justify-between">
<div class="text-sm font-normal text-secondary flex flex-row items-center">Request type</div
>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={webhookType}>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={webhookType} let:item>
<ToggleButton
label="Async"
value="async"
tooltip="The returning value is the uuid of the job assigned to execute the job."
{item}
/>
<ToggleButton
label="Sync"
value="sync"
tooltip="Triggers the execution, wait for the job to complete and return it as a response."
{item}
/>
</ToggleButtonGroup>
</div>
<div class="flex flex-row justify-between">
<div class="text-sm font-normal text-secondary flex flex-row items-center">Call method</div>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={requestType}>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={requestType} let:item>
<ToggleButton
label="POST by path"
value="path"
icon={ArrowUpRight}
{item}
selectedColor="#fb923c"
/>
{#if !isFlow}
@@ -337,6 +340,7 @@ done`
icon={ArrowUpRight}
selectedColor="#fb923c"
disabled={!hash}
{item}
/>
{/if}
@@ -345,6 +349,7 @@ done`
value="get_path"
icon={ArrowDownRight}
disabled={webhookType !== 'sync'}
{item}
selectedColor="#14b8a6"
/>
</ToggleButtonGroup>
@@ -353,9 +358,9 @@ done`
<div class="text-sm font-normal text-secondary flex flex-row items-center"
>Token configuration</div
>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={tokenType}>
<ToggleButton label="Token in Headers" value="headers" />
<ToggleButton label="Token in Query" value="query" />
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={tokenType} let:item>
<ToggleButton label="Token in Headers" value="headers" {item} />
<ToggleButton label="Token in Query" value="query" {item} />
</ToggleButtonGroup>
</div>
</div>
@@ -102,9 +102,10 @@
url = ev.detail === 'runnable' ? '$script:' : ''
url_runnable_args = {}
}}
let:item
>
<ToggleButton value="static" label="Static URL" />
<ToggleButton value="runnable" label="Runnable result as URL" />
<ToggleButton value="static" label="Static URL" {item} />
<ToggleButton value="runnable" label="Runnable result as URL" {item} />
</ToggleButtonGroup>
</div>
{#if url?.startsWith('$')}
@@ -46,7 +46,7 @@
</svelte:fragment>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 gap-4 p-4 max-h-[50vh] overflow-y-auto">
<div class="flex flex-col w-96 gap-4 p-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
@@ -102,7 +102,7 @@
</svelte:fragment>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<span class="text-sm mb-2 leading-6 font-semibold">
Column definitions
<Tooltip
@@ -35,7 +35,7 @@
</svelte:fragment>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
@@ -127,7 +127,7 @@
placement: 'left-start',
middleware: [offset(8), flip(), shift()]
}}
contentClasses="max-h-[50vh] overflow-y-auto p-4 flex flex-col gap-4 w-96"
contentClasses="max-h-[70vh] overflow-y-auto p-4 flex flex-col gap-4 w-96"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
@@ -45,7 +45,7 @@
</svelte:fragment>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
@@ -244,9 +244,9 @@
/>
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="Kafka trigger" icon={KafkaIcon} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="Kafka trigger" icon={KafkaIcon} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
@@ -208,223 +208,230 @@
f={(x) => (x.summary ?? '') + ' ' + x.path + ' (' + x.script_path + ')'}
/>
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.triggers}
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
<p class="font-bold">Unauthorized</p>
<p>Page not available for operators</p>
</div>
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.triggers}
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
<p class="font-bold">Unauthorized</p>
<p>Page not available for operators</p>
</div>
{:else}
<CenteredPage>
<PageHeader
title="NATS triggers"
tooltip="Windmill can consume NATS events and trigger scripts or flows based on them."
>
<Button size="md" startIcon={{ icon: Plus }} on:click={() => natsTriggerEditor.openNew(false)}>
New&nbsp;NATS trigger
</Button>
</PageHeader>
<CenteredPage>
<PageHeader
title="NATS triggers"
tooltip="Windmill can consume NATS events and trigger scripts or flows based on them."
>
<Button
size="md"
startIcon={{ icon: Plus }}
on:click={() => natsTriggerEditor.openNew(false)}
>
New&nbsp;NATS trigger
</Button>
</PageHeader>
{#if isCloudHosted()}
<Alert title="Not compatible with multi-tenant cloud" type="warning">
NATS triggers are disabled in the multi-tenant cloud.
</Alert>
<div class="py-4" />
{/if}
<div class="w-full h-full flex flex-col">
<div class="w-full pb-4 pt-6">
<input
type="text"
placeholder="Search NATS triggers"
bind:value={filter}
class="search-item"
/>
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="NATS trigger" icon={NatsIcon} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
</ToggleButtonGroup>
{#if isCloudHosted()}
<Alert title="Not compatible with multi-tenant cloud" type="warning">
NATS triggers are disabled in the multi-tenant cloud.
</Alert>
<div class="py-4" />
{/if}
<div class="w-full h-full flex flex-col">
<div class="w-full pb-4 pt-6">
<input
type="text"
placeholder="Search NATS triggers"
bind:value={filter}
class="search-item"
/>
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="NATS trigger" icon={NatsIcon} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
<div class="flex flex-row items-center justify-end gap-4">
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
<Toggle
size="xs"
bind:checked={filterUserFolders}
options={{ right: `Only u/${$userStore.username} and f/*` }}
/>
{/if}
</div>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
{#if loading}
{#each new Array(6) as _}
<Skeleton layout={[[6], 0.4]} />
{/each}
{:else if !triggers?.length}
<div class="text-center text-sm text-tertiary mt-2"> No NATS triggers </div>
{:else if items?.length}
<div class="border rounded-md divide-y">
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, nats_resource_path, subjects, extra_perms, canWrite, marked, server_id, error, last_server_ping, enabled } (path)}
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
<div class="flex flex-row items-center justify-end gap-4">
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
<Toggle
size="xs"
bind:checked={filterUserFolders}
options={{ right: `Only u/${$userStore.username} and f/*` }}
/>
{/if}
</div>
</div>
{#if loading}
{#each new Array(6) as _}
<Skeleton layout={[[6], 0.4]} />
{/each}
{:else if !triggers?.length}
<div class="text-center text-sm text-tertiary mt-2"> No NATS triggers </div>
{:else if items?.length}
<div class="border rounded-md divide-y">
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, nats_resource_path, subjects, extra_perms, canWrite, marked, server_id, error, last_server_ping, enabled } (path)}
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
{@const ping = last_server_ping ? new Date(last_server_ping) : undefined}
{@const pinging = ping && ping.getTime() > new Date().getTime() - 15 * 1000}
<div
class="hover:bg-surface-hover w-full items-center px-4 py-2 gap-4 first-of-type:!border-t-0
<div
class="hover:bg-surface-hover w-full items-center px-4 py-2 gap-4 first-of-type:!border-t-0
first-of-type:rounded-t-md last-of-type:rounded-b-md flex flex-col"
>
<div class="w-full flex gap-5 items-center">
<RowIcon kind={is_flow ? 'flow' : 'script'} />
>
<div class="w-full flex gap-5 items-center">
<RowIcon kind={is_flow ? 'flow' : 'script'} />
<a
href="#{path}"
on:click={() => natsTriggerEditor?.openEdit(path, is_flow)}
class="min-w-0 grow hover:underline decoration-gray-400"
>
<div class="text-primary flex-wrap text-left text-md font-semibold mb-1 truncate">
{#if marked}
<span class="text-xs">
{@html marked}
</span>
{:else}
{nats_resource_path} - {subjects.join(', ')}
<a
href="#{path}"
on:click={() => natsTriggerEditor?.openEdit(path, is_flow)}
class="min-w-0 grow hover:underline decoration-gray-400"
>
<div class="text-primary flex-wrap text-left text-md font-semibold mb-1 truncate">
{#if marked}
<span class="text-xs">
{@html marked}
</span>
{:else}
{nats_resource_path} - {subjects.join(', ')}
{/if}
</div>
<div class="text-secondary text-xs truncate text-left font-light">
{path}
</div>
<div class="text-secondary text-xs truncate text-left font-light">
runnable: {script_path}
</div>
</a>
<div class="hidden lg:flex flex-row gap-1 items-center">
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
<div class="w-10">
{#if (enabled && (!pinging || error)) || (!enabled && error) || (enabled && !server_id)}
<Popover notClickable>
<span class="flex h-4 w-4">
<Circle
class="text-red-600 animate-ping absolute inline-flex fill-current"
size={12}
/>
<Circle class="text-red-600 relative inline-flex fill-current" size={12} />
</span>
<div slot="text">
{#if enabled}
{#if !server_id}
Consumer is starting...
{:else}
Consumer is not connected{error ? ': ' + error : ''}
{/if}
{:else}
Consumer was disabled because of an error: {error}
{/if}
</div>
</Popover>
{:else if enabled}
<Popover notClickable>
<span class="flex h-4 w-4">
<Circle
class="text-green-600 relative inline-flex fill-current"
size={12}
/>
</span>
<div slot="text"> Consumer is connected </div>
</Popover>
{/if}
</div>
<div class="text-secondary text-xs truncate text-left font-light">
{path}
</div>
<div class="text-secondary text-xs truncate text-left font-light">
runnable: {script_path}
</div>
</a>
<div class="hidden lg:flex flex-row gap-1 items-center">
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
<div class="w-10">
{#if (enabled && (!pinging || error)) || (!enabled && error) || (enabled && !server_id)}
<Popover notClickable>
<span class="flex h-4 w-4">
<Circle
class="text-red-600 animate-ping absolute inline-flex fill-current"
size={12}
/>
<Circle class="text-red-600 relative inline-flex fill-current" size={12} />
</span>
<div slot="text">
{#if enabled}
{#if !server_id}
Consumer is starting...
{:else}
Consumer is not connected{error ? ': ' + error : ''}
{/if}
{:else}
Consumer was disabled because of an error: {error}
{/if}
</div>
</Popover>
{:else if enabled}
<Popover notClickable>
<span class="flex h-4 w-4">
<Circle class="text-green-600 relative inline-flex fill-current" size={12} />
</span>
<div slot="text"> Consumer is connected </div>
</Popover>
{/if}
</div>
<Toggle
checked={enabled}
disabled={!canWrite}
on:change={(e) => {
setTriggerEnabled(path, e.detail)
}}
/>
<div class="flex gap-2 items-center justify-end">
<Button
on:click={() => natsTriggerEditor?.openEdit(path, is_flow)}
size="xs"
startIcon={canWrite
? { icon: Pen }
: {
icon: Eye
}}
color="dark"
>
{canWrite ? 'Edit' : 'View'}
</Button>
<Dropdown
items={[
{
displayName: `View ${is_flow ? 'Flow' : 'Script'}`,
icon: Eye,
action: () => {
goto(href)
}
},
{
displayName: 'Delete',
type: 'delete',
icon: Trash,
disabled: !canWrite,
action: async () => {
await NatsTriggerService.deleteNatsTrigger({
workspace: $workspaceStore ?? '',
path
})
loadTriggers()
}
},
{
displayName: canWrite ? 'Edit' : 'View',
icon: canWrite ? Pen : Eye,
action: () => {
natsTriggerEditor?.openEdit(path, is_flow)
}
},
{
displayName: 'Audit logs',
icon: Eye,
href: `${base}/audit_logs?resource=${path}`
},
{
displayName: canWrite ? 'Share' : 'See Permissions',
icon: Share,
action: () => {
shareModal.openDrawer(path, 'nats_trigger')
}
}
]}
<Toggle
checked={enabled}
disabled={!canWrite}
on:change={(e) => {
setTriggerEnabled(path, e.detail)
}}
/>
<div class="flex gap-2 items-center justify-end">
<Button
on:click={() => natsTriggerEditor?.openEdit(path, is_flow)}
size="xs"
startIcon={canWrite
? { icon: Pen }
: {
icon: Eye
}}
color="dark"
>
{canWrite ? 'Edit' : 'View'}
</Button>
<Dropdown
items={[
{
displayName: `View ${is_flow ? 'Flow' : 'Script'}`,
icon: Eye,
action: () => {
goto(href)
}
},
{
displayName: 'Delete',
type: 'delete',
icon: Trash,
disabled: !canWrite,
action: async () => {
await NatsTriggerService.deleteNatsTrigger({
workspace: $workspaceStore ?? '',
path
})
loadTriggers()
}
},
{
displayName: canWrite ? 'Edit' : 'View',
icon: canWrite ? Pen : Eye,
action: () => {
natsTriggerEditor?.openEdit(path, is_flow)
}
},
{
displayName: 'Audit logs',
icon: Eye,
href: `${base}/audit_logs?resource=${path}`
},
{
displayName: canWrite ? 'Share' : 'See Permissions',
icon: Share,
action: () => {
shareModal.openDrawer(path, 'nats_trigger')
}
}
]}
/>
</div>
</div>
</div>
<div class="w-full flex justify-between items-baseline">
<div
class="flex flex-wrap text-[0.7em] text-tertiary gap-1 items-center justify-end truncate pr-2"
><div class="truncate">edited by {edited_by}</div><div class="truncate"
>the {displayDate(edited_at)}</div
<div class="w-full flex justify-between items-baseline">
<div
class="flex flex-wrap text-[0.7em] text-tertiary gap-1 items-center justify-end truncate pr-2"
><div class="truncate">edited by {edited_by}</div><div class="truncate"
>the {displayDate(edited_at)}</div
></div
></div
></div
>
</div>
{/each}
</div>
{:else}
<NoItemFound />
>
</div>
{/each}
</div>
{:else}
<NoItemFound />
{/if}
</div>
{#if items && items?.length > 15 && nbDisplayed < items.length}
<span class="text-xs"
>{nbDisplayed} items out of {items.length}
<button class="ml-4" on:click={() => (nbDisplayed += 30)}>load 30 more</button></span
>
{/if}
</div>
{#if items && items?.length > 15 && nbDisplayed < items.length}
<span class="text-xs"
>{nbDisplayed} items out of {items.length}
<button class="ml-4" on:click={() => (nbDisplayed += 30)}>load 30 more</button></span
>
{/if}
</CenteredPage>
</CenteredPage>
{/if}
<ShareModal
@@ -237,9 +237,9 @@
/>
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="Postgres trigger" icon={Database} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="Postgres trigger" icon={Database} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
@@ -183,9 +183,9 @@
<input type="text" placeholder="Search routes" bind:value={filter} class="search-item" />
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="Route" icon={Route} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="Route" icon={Route} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
@@ -710,13 +710,16 @@
<div class="absolute right-0 -mt-6">
<div class="flex flex-row justify-between items-center">
<ToggleButtonGroup
bind:selected={graph}
on:selected={() => {
selected={graph}
on:selected={({ detail }) => {
graph = detail
graphIsRunsChart = graph === 'RunChart'
}}
let:item
>
<ToggleButton value="RunChart" label="Duration" />
<ToggleButton value="RunChart" label="Duration" {item} />
<ToggleButton
{item}
value="ConcurrencyChart"
label="Concurrency"
icon={warnJobLimit ? AlertTriangle : undefined}
@@ -1090,13 +1093,15 @@
<div class="relative z-10">
<div class="absolute right-2">
<ToggleButtonGroup
bind:selected={graph}
on:selected={() => {
selected={graph}
on:selected={({ detail }) => {
graph = detail
graphIsRunsChart = graph == 'RunChart'
}}
let:item
>
<ToggleButton value="RunChart" label="Duration" />
<ToggleButton value="ConcurrencyChart" label="Concurrency" />
<ToggleButton value="RunChart" label="Duration" {item} />
<ToggleButton value="ConcurrencyChart" label="Concurrency" {item} />
</ToggleButtonGroup>
{#if !graphIsRunsChart}
<DropdownV2
@@ -273,18 +273,18 @@
<input type="text" placeholder="Search schedule" bind:value={filter} class="search-item" />
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="schedule" label="Schedule" icon={Calendar} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="schedule" label="Schedule" icon={Calendar} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
<div class="flex flex-row items-center justify-end gap-4">
<ToggleButtonGroup class="h-6 w-auto" bind:selected={filterEnabledDisabled}>
<ToggleButton small value="all" label="All" />
<ToggleButton small value="enabled" label="Enabled" />
<ToggleButton small value="disabled" label="Disabled" />
<ToggleButtonGroup class="h-6 w-auto" bind:selected={filterEnabledDisabled} let:item>
<ToggleButton small value="all" label="All" {item} />
<ToggleButton small value="enabled" label="Enabled" {item} />
<ToggleButton small value="disabled" label="Disabled" {item} />
</ToggleButtonGroup>
{#if $userStore?.is_super_admin && $userStore.username.includes('@')}
<Toggle size="xs" bind:checked={filterUserFolders} options={{ right: 'Only f/*' }} />
@@ -140,15 +140,15 @@
(x) =>
x.path.startsWith(ownerFilter + '/') &&
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
)
)
: triggers?.filter(
(x) =>
x.script_path.startsWith(ownerFilter + '/') &&
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
)
)
: triggers?.filter((x) =>
filterItemsPathsBaseOnUserFilters(x, selectedFilterKind, filterUserFolders)
)
)
$: if ($workspaceStore) {
ownerFilter = undefined
@@ -158,10 +158,10 @@
selectedFilterKind === 'trigger'
? Array.from(
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
).sort()
: Array.from(
new Set(filteredItems?.map((x) => x.script_path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
).sort()
$: items = filter !== '' ? filteredItems : preFilteredItems
@@ -230,9 +230,9 @@
/>
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="SQS trigger" icon={Database} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="SQS trigger" icon={Database} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
@@ -337,7 +337,7 @@
? { icon: Pen }
: {
icon: Eye
}}
}}
color="dark"
>
{canWrite ? 'Edit' : 'View'}
@@ -233,8 +233,8 @@
{/if}
</label>
{/if}
<label class="block pb-4">
<div class="flex flex-col gap-1">
<div class="block pb-4">
<label for="ai-key" class="flex flex-col gap-1">
<span class="text-secondary text-sm">
AI key for Windmill AI
<Tooltip>
@@ -246,19 +246,22 @@
</Tooltip>
<span class="text-2xs text-tertiary ml-2">(optional but recommended)</span>
</span>
<div class="pb-2">
<ToggleButtonGroup bind:selected>
<ToggleButton value="openai" label="OpenAI" />
<ToggleButton value="anthropic" label="Anthropic" />
<ToggleButton value="mistral" label="Mistral" />
<ToggleButton value="deepseek" label="DeepSeek" />
<ToggleButton value="groq" label="Groq" />
<ToggleButton value="openrouter" label="OpenRouter" />
<ToggleButtonGroup bind:selected let:item>
<ToggleButton value="openai" label="OpenAI" {item} />
<ToggleButton value="anthropic" label="Anthropic" {item} />
<ToggleButton value="mistral" label="Mistral" {item} />
<ToggleButton value="deepseek" label="DeepSeek" {item} />
<ToggleButton value="groq" label="Groq" {item} />
<ToggleButton value="openrouter" label="OpenRouter" {item} />
</ToggleButtonGroup>
</div>
</div>
</label>
<div class="flex flex-row gap-1 pb-4">
<input
id="ai-key"
type="password"
autocomplete="new-password"
bind:value={aiKey}
@@ -278,7 +281,7 @@
options={{ right: 'Enable code completion' }}
/>
{/if}
</label>
</div>
<Toggle
disabled={isCloudHosted() && !isDomainAllowed}
bind:checked={auto_invite}
@@ -304,9 +307,10 @@
on:selected={(e) => {
operatorOnly = e.detail == 'operator'
}}
let:item
>
<ToggleButton value="operator" size="xs" label="Operator" />
<ToggleButton value="developer" size="xs" label="Developer" />
<ToggleButton value="operator" size="xs" label="Operator" {item} />
<ToggleButton value="developer" size="xs" label="Developer" {item} />
</ToggleButtonGroup>
</div>
<div class="flex flex-wrap flex-row justify-between pt-10 gap-1">
@@ -232,9 +232,9 @@
<input type="text" placeholder="Search WS triggers" bind:value={filter} class="search-item" />
<div class="flex flex-row items-center gap-2 mt-6">
<div class="text-sm shrink-0"> Filter by path of </div>
<ToggleButtonGroup bind:selected={selectedFilterKind}>
<ToggleButton small value="trigger" label="WS trigger" icon={Unplug} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} />
<ToggleButtonGroup bind:selected={selectedFilterKind} let:item>
<ToggleButton small value="trigger" label="WS trigger" icon={Unplug} {item} />
<ToggleButton small value="script_flow" label="Script/Flow" icon={Code} {item} />
</ToggleButtonGroup>
</div>
<ListFilters syncQuery bind:selectedFilter={ownerFilter} filters={owners} />
@@ -1069,20 +1069,22 @@
aiModels = []
codeCompletionModel = undefined
}}
let:item
>
<ToggleButton value="openai" label="OpenAI" />
<ToggleButton value="anthropic" label="Anthropic" />
<ToggleButton value="mistral" label="Mistral" />
<ToggleButton value="deepseek" label="DeepSeek" />
<ToggleButton value="googleai" label="Google AI" />
<ToggleButton value="groq" label="Groq" />
<ToggleButton value="openrouter" label="OpenRouter" />
<ToggleButton value="openai" label="OpenAI" {item} />
<ToggleButton value="anthropic" label="Anthropic" {item} />
<ToggleButton value="mistral" label="Mistral" {item} />
<ToggleButton value="deepseek" label="DeepSeek" {item} />
<ToggleButton value="googleai" label="Google AI" {item} />
<ToggleButton value="groq" label="Groq" {item} />
<ToggleButton value="openrouter" label="OpenRouter" {item} />
<ToggleButton
value="customai"
label={'Custom AI' + ($enterpriseLicense ? '' : ' (EE)')}
disabled={!$enterpriseLicense}
tooltip="Configure a custom AI provider that is OpenAI API compatible"
showTooltipIcon
{item}
/>
</ToggleButtonGroup>
<div class="flex gap-1">