migrate popup to melt popover (3/4) (#5328)

* 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 popup to melt popover

* 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

* 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

---------

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>
This commit is contained in:
Guilhem
2025-02-25 08:53:43 +00:00
committed by GitHub
parent 5179266259
commit 87cda7a4d7
69 changed files with 2861 additions and 2765 deletions
+54 -54
View File
@@ -2,7 +2,8 @@
import { createEventDispatcher } from 'svelte'
import { globalEmailInvite, superadmin, workspaceStore } from '$lib/stores'
import { SettingService, UserService, WorkspaceService } from '$lib/gen'
import { Button, Popup } from './common'
import { Button } from './common'
import Popover from './meltComponents/Popover.svelte'
import { sendUserToast } from '$lib/toast'
import { isCloudHosted } from '$lib/cloud'
import { goto } from '$lib/navigation'
@@ -72,64 +73,63 @@
let selected: 'operator' | 'developer' | 'admin' = 'developer'
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Popover placement="bottom-end">
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: UserPlus }}>
Add new user
</Button>
</svelte:fragment>
<div class="flex flex-col w-72 p-2">
<span class="text-sm mb-2 leading-6 font-semibold">Add a new user</span>
<svelte:fragment slot="content">
<div class="flex flex-col w-72 p-4">
<span class="text-sm mb-2 leading-6 font-semibold">Add a new user</span>
<span class="text-xs mb-1 leading-6">Email</span>
<input type="email mb-1" on:keyup={handleKeyUp} placeholder="email" bind:value={email} />
<span class="text-xs mb-1 leading-6">Email</span>
<input type="email mb-1" on:keyup={handleKeyUp} placeholder="email" bind:value={email} />
{#if !automateUsernameCreation}
<span class="text-xs mb-1 pt-2 leading-6">Username</span>
<input type="text" on:keyup={handleKeyUp} placeholder="username" bind:value={username} />
{/if}
{#if !automateUsernameCreation}
<span class="text-xs mb-1 pt-2 leading-6">Username</span>
<input type="text" on:keyup={handleKeyUp} placeholder="username" bind:value={username} />
{/if}
<span class="text-xs mb-1 pt-2 leading-6">Role</span>
<ToggleButtonGroup bind:selected class="mb-4">
<ToggleButton
value="operator"
<span class="text-xs mb-1 pt-2 leading-6">Role</span>
<ToggleButtonGroup bind:selected class="mb-4">
<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."
/>
<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)."
/>
<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."
/>
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
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."
/>
<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)."
/>
<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."
/>
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
size="sm"
on:click={() => {
addUser().then(() => {
// @ts-ignore
email = undefined
// @ts-ignore
username = undefined
})
}}
disabled={email === undefined || (!automateUsernameCreation && username === undefined)}
>
Add
</Button>
</div>
</Popup>
on:click={() => {
addUser().then(() => {
// @ts-ignore
email = undefined
// @ts-ignore
username = undefined
})
}}
disabled={email === undefined || (!automateUsernameCreation && username === undefined)}
>
Add
</Button>
</div>
</svelte:fragment>
</Popover>
@@ -8,7 +8,7 @@
import Toggle from './Toggle.svelte'
import TestConnection from './TestConnection.svelte'
import SupabaseIcon from './icons/SupabaseIcon.svelte'
import Popup from './common/popup/Popup.svelte'
import Popover from './meltComponents/Popover.svelte'
import Button from './common/button/Button.svelte'
import { Loader2 } from 'lucide-svelte'
@@ -129,13 +129,12 @@
/>
<TestConnection {resourceType} {args} />
{#if resourceType == 'postgresql'}
<Popup
let:close
<Popover
floatingConfig={{
placement: 'bottom'
}}
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
spacingSize="sm"
size="xs"
@@ -147,32 +146,34 @@
From connection string
</Button>
</svelte:fragment>
<div class="block text-primary">
<div class="w-[550px] flex flex-col items-start gap-1">
<div class="flex flex-row gap-1 w-full">
<input
type="text"
bind:value={connectionString}
placeholder="postgres://user:password@host:5432/dbname?sslmode=disable"
/>
<Button
size="xs"
color="blue"
buttonType="button"
on:click={() => {
parseConnectionString(close)
}}
disabled={connectionString.length <= 0}
>
Apply
</Button>
<svelte:fragment slot="content" let:close>
<div class="block text-primary p-4">
<div class="w-[550px] flex flex-col items-start gap-1">
<div class="flex flex-row gap-1 w-full">
<input
type="text"
bind:value={connectionString}
placeholder="postgres://user:password@host:5432/dbname?sslmode=disable"
/>
<Button
size="xs"
color="blue"
buttonType="button"
on:click={() => {
parseConnectionString(close)
}}
disabled={connectionString.length <= 0}
>
Apply
</Button>
</div>
{#if !validConnectionString}
<p class="text-red-500 text-xs">Could not parse connection string</p>
{/if}
</div>
{#if !validConnectionString}
<p class="text-red-500 text-xs">Could not parse connection string</p>
{/if}
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
{/if}
{#if resourceType == 'postgresql' && supabaseWizard}
<a
@@ -1,6 +1,7 @@
<script lang="ts">
import { Button, Popup } from './common'
import { Button } from './common'
import { Pen } from 'lucide-svelte'
import Popover from './meltComponents/Popover.svelte'
import Tooltip from './Tooltip.svelte'
import AssignableTagsInner from './AssignableTagsInner.svelte'
@@ -11,12 +12,13 @@
export let showWorkspaceRestriction = false
</script>
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: placement }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
{disabled}
closeButton
usePointerDownOutside
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button {color} size="xs" nonCaptureEvent={true} {disabled}>
<div class="flex flex-row gap-1 items-center"
><Pen size={14} /> Custom tags&nbsp;<Tooltip light
@@ -28,5 +30,7 @@
>
</Button>
</svelte:fragment>
<AssignableTagsInner {showWorkspaceRestriction} on:refresh />
</Popup>
<svelte:fragment slot="content">
<AssignableTagsInner {showWorkspaceRestriction} on:refresh />
</svelte:fragment>
</Popover>
@@ -29,7 +29,7 @@
loadCustomTags()
</script>
<div class="flex flex-col w-72 p-2 gap-2">
<div class="flex flex-col w-72 p-4 gap-2">
{#if customTags == undefined}
<Loader2 class="animate-spin" />
{:else}
@@ -1,5 +1,6 @@
<script lang="ts">
import { Button, Popup } from './common'
import { Button } from './common'
import Popover from './meltComponents/Popover.svelte'
import { autoPlacement } from '@floating-ui/core'
import ChangeInstanceUsernameInner from './ChangeInstanceUsernameInner.svelte'
@@ -8,7 +9,7 @@
export let isConflict = false
</script>
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -16,19 +17,20 @@
})
]
}}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
closeButton
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button color={isConflict ? 'red' : 'light'} size="xs" spacingSize="xs2" nonCaptureEvent={true}
>{isConflict ? 'Fix username conflict' : 'Change username'}</Button
>
</svelte:fragment>
<ChangeInstanceUsernameInner
{email}
{username}
{isConflict}
on:close={() => close(null)}
on:renamed
/>
</Popup>
<svelte:fragment slot="content">
<ChangeInstanceUsernameInner
{email}
{username}
{isConflict}
on:close={() => close()}
on:renamed
/>
</svelte:fragment>
</Popover>
@@ -8,6 +8,7 @@
export let email: string
export let username: string
export let isConflict = false
export let noPadding = false
let loading = false
@@ -71,11 +72,9 @@
}
</script>
<div class="flex flex-col max-w-2xl p-2">
{#if isConflict}
<span class="text-sm mb-2 leading-6 font-semibold"
>Fix username conflict</span
>
<div class="flex flex-col max-w-2xl {noPadding ? '' : 'p-4'}">
{#if isConflict}
<span class="text-sm mb-2 leading-6 font-semibold">Fix username conflict</span>
{/if}
<span class="text-sm font-semibold mb-1 leading-6"
+12 -13
View File
@@ -1,22 +1,21 @@
<script lang="ts">
import Section from './Section.svelte'
import { Button, Popup } from './common'
import { Button } from './common'
import { Clock } from 'lucide-svelte'
import Popover from './meltComponents/Popover.svelte'
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }} closeButton>
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Clock }}>
Use simplified builder
</Button>
</svelte:fragment>
<Section label="CRON Builder">
<div class="flex flex-col w-72">
<slot {close} />
</div>
</Section>
</Popup>
<svelte:fragment slot="content" let:close>
<Section label="CRON Builder" wrapperClass="p-4">
<div class="flex flex-col w-72">
<slot {close} />
</div>
</Section>
</svelte:fragment>
</Popover>
+1 -1
View File
@@ -385,7 +385,7 @@
size="xs"
on:click={() => {
schedule = nschedule
close(null)
close()
}}
>
Set cron schedule
@@ -1,7 +1,8 @@
<script lang="ts">
import { Button, Popup } from './common'
import { Button } from './common'
import { Pen } from 'lucide-svelte'
import Tooltip from './Tooltip.svelte'
import Popover from './meltComponents/Popover.svelte'
import DefaultTagsInner from './DefaultTagsInner.svelte'
@@ -11,11 +12,8 @@
let placement: 'bottom-end' | 'top-end' = 'bottom-end'
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: placement }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: placement }} contentClasses="p-4">
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true}>
<div class="flex flex-row gap-1 items-center"
><Pen size={14} /> Default tags&nbsp;<Tooltip light
@@ -25,5 +23,7 @@
>
</Button>
</svelte:fragment>
<DefaultTagsInner bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
</Popup>
<svelte:fragment slot="content">
<DefaultTagsInner bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
</svelte:fragment>
</Popover>
@@ -18,7 +18,7 @@
import { sendUserToast } from '$lib/toast'
import Toggle from './Toggle.svelte'
import { emptyString } from '$lib/utils'
import Popup from './common/popup/Popup.svelte'
import Popover from './meltComponents/Popover.svelte'
import SchemaFormDnd from './schema/SchemaFormDND.svelte'
import { deepEqual } from 'fast-equals'
import { tweened } from 'svelte/motion'
@@ -371,12 +371,8 @@
{argName}
{#if !uiOnly}
<div on:click|stopPropagation|preventDefault>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Popover placement="bottom-end" containerClasses="p-4" closeButton>
<svelte:fragment slot="trigger">
<Button
color="light"
size="xs2"
@@ -385,34 +381,36 @@
iconOnly
/>
</svelte:fragment>
<Label label="Name">
<div class="flex flex-col gap-2">
<input
type="text"
class="w-full !bg-surface"
value={argName}
id={argName + i}
on:keydown={(event) => {
if (event.key === 'Enter') {
<svelte:fragment slot="content" let:close>
<Label label="Name" class="p-4">
<div class="flex flex-col gap-2">
<input
type="text"
class="w-full !bg-surface"
value={argName}
id={argName + i}
on:keydown={(event) => {
if (event.key === 'Enter') {
renameProperty(argName, argName + i)
close()
}
}}
/>
<Button
variant="border"
color="light"
size="xs"
on:click={() => {
renameProperty(argName, argName + i)
close(null)
}
}}
/>
<Button
variant="border"
color="light"
size="xs"
on:click={() => {
renameProperty(argName, argName + i)
close(null)
}}
>
Rename
</Button>
</div>
</Label>
</Popup>
close()
}}
>
Rename
</Button>
</div>
</Label>
</svelte:fragment>
</Popover>
</div>
{/if}
</div>
@@ -2,19 +2,21 @@
import { Settings } from 'lucide-svelte'
import FormatOnSave from './FormatOnSave.svelte'
import VimMode from './VimMode.svelte'
import { Button, Popup } from './common'
import { Button } from './common'
import CodeCompletionStatus from './copilot/CodeCompletionStatus.svelte'
import type { EditorBarUi } from './custom_ui'
import Popover from './meltComponents/Popover.svelte'
export let customUi: EditorBarUi = {}
</script>
{#if customUi?.autoformatting != false || customUi?.vimMode != false || customUi?.aiCompletion != false}
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
usePointerDownOutside
contentClasses="flex flex-col gap-y-2 p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
btnClasses="text-tertiary"
color="light"
@@ -26,7 +28,7 @@
/>
</svelte:fragment>
<div class="flex flex-col gap-y-2">
<svelte:fragment slot="content">
{#if customUi?.autoformatting != false}
<div>
<FormatOnSave />
@@ -42,6 +44,6 @@
<CodeCompletionStatus />
</div>
{/if}
</div>
</Popup>
</svelte:fragment>
</Popover>
{/if}
@@ -1,7 +1,8 @@
<script lang="ts">
import { type Job, JobService, type Flow, type RestartedFrom, type OpenFlow } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Badge, Button, Popup } from './common'
import { Badge, Button } from './common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
import { runFlowPreview } from './flows/utils'
@@ -261,7 +262,10 @@
</Badge>
</Button>
{:else}
<Popup floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}
contentClasses="p-4"
>
<svelte:fragment slot="button">
<Button
title={`Re-start this flow from step ${selectedJobStep} (included).`}
@@ -283,49 +287,51 @@
</Badge>
</Button>
</svelte:fragment>
<label class="block text-primary">
<div class="pb-1 text-sm text-secondary"
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
>
<div class="flex w-full">
{#if selectedJobStepType === 'forloop'}
<input
type="number"
min="0"
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
/>
{:else}
<select
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
>
{#each restartBranchNames as [branchIdx, branchName]}
<option value={branchIdx}>{branchName}</option>
{/each}
</select>
{/if}
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Restart flow"
on:click|once={() => {
runPreview($previewArgs, {
flow_job_id: jobId,
step_id: selectedJobStep,
branch_or_iteration_n: branchOrIterationN
})
}}
<svelte:fragment slot="content">
<label class="block text-primary p-4">
<div class="pb-1 text-sm text-secondary"
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
>
<ArrowRight size={18} />
</Button>
</div>
</label>
</Popup>
<div class="flex w-full">
{#if selectedJobStepType === 'forloop'}
<input
type="number"
min="0"
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
/>
{:else}
<select
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
>
{#each restartBranchNames as [branchIdx, branchName]}
<option value={branchIdx}>{branchName}</option>
{/each}
</select>
{/if}
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Restart flow"
on:click|once={() => {
runPreview($previewArgs, {
flow_job_id: jobId,
step_id: selectedJobStep,
branch_or_iteration_n: branchOrIterationN
})
}}
>
<ArrowRight size={18} />
</Button>
</div>
</label>
</svelte:fragment>
</Popover>
{/if}
{/if}
<Button
@@ -2,7 +2,7 @@
import { Pencil } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import Button from './common/button/Button.svelte'
import Popup from './common/popup/Popup.svelte'
import Popover from './meltComponents/Popover.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import ChangeInstanceUsernameInner from './ChangeInstanceUsernameInner.svelte'
import { UserService } from '$lib/gen'
@@ -26,128 +26,125 @@
sendUserToast('Password must be at least 5 characters long', true)
return
}
await UserService.setPasswordForUser({user: email, requestBody:{ password }})
await UserService.setPasswordForUser({ user: email, requestBody: { password } })
sendUserToast(`Password updated for ${email}`)
}
async function saveLoginType() {
await UserService.setLoginTypeForUser({user: email, requestBody:{ login_type }})
await UserService.setLoginTypeForUser({ user: email, requestBody: { login_type } })
sendUserToast(`Login type updated for ${email}`)
dispatch('refresh')
}
</script>
<Popup
let:close
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
closeButton
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button nonCaptureEvent={true} size="xs" color="light" endIcon={{ icon: Pencil }}>Edit</Button>
</svelte:fragment>
<div class="flex flex-col gap-8 max-w-sm">
{#if automateUsernameCreation && username}
<ChangeInstanceUsernameInner {email} {username} on:renamed />
{/if}
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Name</div>
<div class="flex w-full">
<input
type="text"
bind:value
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
saveName()
close(null)
}
<svelte:fragment slot="content">
<div class="flex flex-col gap-8 max-w-sm p-4">
{#if automateUsernameCreation && username}
<ChangeInstanceUsernameInner {email} {username} on:renamed noPadding />
{/if}
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Name</div>
<div class="flex w-full">
<input
type="text"
bind:value
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
saveName()
}
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
on:click={() => {
saveName()
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
on:click={() => {
saveName()
close(null)
}}
>
Update name
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Password</div>
<div class="flex w-full">
<input
type="password"
bind:value={password}
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
savePassword()
close(null)
}
>
Update name
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Password</div>
<div class="flex w-full">
<input
type="password"
bind:value={password}
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
savePassword()
}
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
on:click={() => {
savePassword()
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save ID"
on:click={() => {
savePassword()
close(null)
}}
>
Update password
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Login type</div>
<div class="text-xs text-secondary mb-2">
Must match exact SSO name, "password" or "saml". Examples: password, google, saml, microsoft
</div>
>
Update password
</Button>
</label>
<label class="block text-primary">
<div class="pb-2 text-sm font-semibold text-primary">Login type</div>
<div class="text-xs text-secondary mb-2">
Must match exact SSO name, "password" or "saml". Examples: password, google, saml,
microsoft
</div>
<div class="flex w-full">
<input
type="text"
bind:value={login_type}
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
saveLoginType()
close(null)
}
<div class="flex w-full">
<input
type="text"
bind:value={login_type}
class="!w-auto grow"
on:click|stopPropagation={() => {}}
on:keydown|stopPropagation
on:keypress|stopPropagation={({ key }) => {
if (key === 'Enter') {
saveLoginType()
}
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save login type"
on:click={() => {
saveLoginType()
}}
/>
</div>
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="mt-2 "
aria-label="Save login type"
on:click={() => {
saveLoginType()
close(null)
}}
>
Update login type
</Button>
</label>
</div>
</Popup>
>
Update login type
</Button>
</label>
</div>
</svelte:fragment>
</Popover>
+45 -43
View File
@@ -3,7 +3,8 @@
import { createEventDispatcher } from 'svelte'
import { globalEmailInvite, superadmin, workspaceStore } from '$lib/stores'
import { UserService, WorkspaceService } from '$lib/gen'
import { Button, Popup } from './common'
import { Button } from './common'
import Popover from './meltComponents/Popover.svelte'
import { isCloudHosted } from '$lib/cloud'
import { goto } from '$lib/navigation'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
@@ -64,53 +65,54 @@
let selected: 'operator' | 'developer' | 'admin' = 'developer'
</script>
<Popup floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: MailPlus }}>
Invite
</Button>
</svelte:fragment>
<div class="flex flex-col gap-2">
<input
type="email"
on:keyup={handleKeyUp}
placeholder="email"
bind:value={email}
class="mr-4"
/>
<ToggleButtonGroup bind:selected>
<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."
<svelte:fragment slot="content">
<div class="flex flex-col gap-2 p-4">
<input
type="email"
on:keyup={handleKeyUp}
placeholder="email"
bind:value={email}
class="mr-4"
/>
<ToggleButtonGroup bind:selected>
<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."
/>
<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)."
/>
<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)."
/>
<ToggleButton
position="right"
value="admin"
<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."
/>
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
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."
/>
</ToggleButtonGroup>
<Button
variant="contained"
color="blue"
size="sm"
on:click={inviteUser}
disabled={email === undefined}
>
Invite
</Button>
</div>
</Popup>
on:click={inviteUser}
disabled={email === undefined}
>
Invite
</Button>
</div>
</svelte:fragment>
</Popover>
@@ -4,7 +4,8 @@
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import IconedResourceType from './IconedResourceType.svelte'
import { Button, ClearableInput, Popup } from './common'
import { Button, ClearableInput } from './common'
import Popover from './meltComponents/Popover.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import Label from './Label.svelte'
import Tooltip from './Tooltip.svelte'
@@ -50,20 +51,18 @@
>
Clear
</Button>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4 w-[500px] h-[500px] "
let:close
contentClasses="flex flex-col gap-2 h-full p-4 max-h-[40vh] w-[500px]"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button nonCaptureEvent size="xs" color="dark">Select resource type</Button>
</svelte:fragment>
<div class="flex flex-col gap-2 h-full">
<svelte:fragment slot="content" let:close>
<ClearableInput bind:value={search} placeholder="Search resource..." />
<div class="overflow-y-scroll h-full">
@@ -80,7 +79,7 @@
disabled={notPickable}
on:click={() => {
onClick(undefined)
close(null)
close()
}}
>
None
@@ -96,7 +95,7 @@
disabled={notPickable}
on:click={() => {
onClick(r)
close(null)
close()
}}
>
<IconedResourceType name={r} after={true} width="20px" height="20px" />
@@ -108,8 +107,8 @@
{/if}
</div>
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
</div>
</svelte:fragment>
<div class="flex flex-row items-center w-full justify-between">
+14 -14
View File
@@ -12,7 +12,7 @@
import SharedBadge from './SharedBadge.svelte'
import TimeAgo from './TimeAgo.svelte'
import Popup from './common/popup/Popup.svelte'
import Popover from './meltComponents/Popover.svelte'
import { autoPlacement } from '@floating-ui/core'
import { Calendar, CornerDownLeft } from 'lucide-svelte'
import RunFormAdvancedPopup from './RunFormAdvancedPopup.svelte'
@@ -58,7 +58,6 @@
export let jsonView = false
let reloadArgs = 0
let blockPopupOpen = false
let jsonEditor: JsonInputs | undefined = undefined
let schemaHeight = 0
@@ -206,7 +205,7 @@
{scheduledForStr ? 'Schedule to run later' : buttonText}
</Button>
<div>
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -221,22 +220,23 @@
})
]
}}
let:close
blockOpen={blockPopupOpen}
closeButton
usePointerDownOutside
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button nonCaptureEvent startIcon={{ icon: Calendar }} size="xs" color="light">
Advanced
</Button>
</svelte:fragment>
<RunFormAdvancedPopup
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
bind:runnable
on:close={() => close(null)}
/>
</Popup>
<svelte:fragment slot="content">
<RunFormAdvancedPopup
bind:scheduledForStr
bind:invisible_to_owner
bind:overrideTag
bind:runnable
/>
</svelte:fragment>
</Popover>
</div>
</div>
{#if overrideTag}
@@ -1,7 +1,6 @@
<script lang="ts">
import { SCRIPT_VIEW_SHOW_SCHEDULE_RUN_LATER } from '$lib/consts'
import Label from './Label.svelte'
import CloseButton from './common/CloseButton.svelte'
import Toggle from './Toggle.svelte'
import Tooltip from './Tooltip.svelte'
import { userStore, workerTags, workspaceStore } from '$lib/stores'
@@ -36,15 +35,11 @@
}
</script>
<div class="flex flex-col gap-4 p-2">
<div class="flex flex-col gap-4 p-4">
<div class="flex flex-col gap-2">
{#if SCRIPT_VIEW_SHOW_SCHEDULE_RUN_LATER}
<div class="">
<Label label="Schedule to run later">
<svelte:fragment slot="action">
<CloseButton on:close noBg />
</svelte:fragment>
</Label>
<Label label="Schedule to run later" />
<div class="flex flex-row items-center my-1">
<div>
@@ -11,12 +11,12 @@
import AppSelect from '../../inputs/AppSelect.svelte'
import { twMerge } from 'tailwind-merge'
import { Popup } from '$lib/components/common'
import { Plug2 } from 'lucide-svelte'
import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte'
import { connectOutput } from '$lib/components/apps/editor/appUtils'
import RowWrapper from '../../layout/RowWrapper.svelte'
import type { ICellRendererParams } from 'ag-grid-community'
import Popover from '$lib/components/meltComponents/Popover.svelte'
export let p: ICellRendererParams<any>
export let id: string
@@ -124,34 +124,38 @@
{#if $connectingInput.opened}
<div class="absolute z-50 left-8 -top-[10px]">
<Popup
<Popover
floatingConfig={{
strategy: 'absolute',
placement: 'bottom-start'
}}
closeOnOtherPopoverOpen
contentClasses="p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<button
class="bg-red-500/70 border border-red-600 px-1 py-0.5"
title="Outputs"
aria-label="Open output"><Plug2 size={12} /></button
>
</svelte:fragment>
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) => {
const tableId = action.id.split('_')[0]
<svelte:fragment slot="content">
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) => {
const tableId = action.id.split('_')[0]
connectOutput(
connectingInput,
action.type,
tableId,
`inputs.${action.id}[${rowIndex}].${detail}`
)
}}
componentId={action.id}
/>
</Popup>
connectOutput(
connectingInput,
action.type,
tableId,
`inputs.${action.id}[${rowIndex}].${detail}`
)
}}
componentId={action.id}
/>
</svelte:fragment>
</Popover>
</div>
{/if}
{/if}
@@ -39,12 +39,12 @@
import AppSelect from '../../inputs/AppSelect.svelte'
import RowWrapper from '../../layout/RowWrapper.svelte'
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
import { Popup } from '$lib/components/common'
import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte'
import { EyeIcon, Plug2 } from 'lucide-svelte'
import AppCell from './AppCell.svelte'
import sum from 'hash-sum'
import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -553,31 +553,35 @@
{#if $connectingInput.opened}
<div class="absolute z-50 left-8 -top-[10px]">
<Popup
<Popover
floatingConfig={{
strategy: 'absolute',
placement: 'bottom-start'
}}
closeOnOtherPopoverOpen
contentClasses="p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<button
class="bg-red-500/70 border border-red-600 px-1 py-0.5"
title="Outputs"
aria-label="Open output"><Plug2 size={12} /></button
>
</svelte:fragment>
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) =>
connectOutput(
connectingInput,
'buttoncomponent',
actionButton.id,
detail
)}
componentId={actionButton.id}
/>
</Popup>
<svelte:fragment slot="content">
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) =>
connectOutput(
connectingInput,
'buttoncomponent',
actionButton.id,
detail
)}
componentId={actionButton.id}
/>
</svelte:fragment>
</Popover>
</div>
{/if}
{/if}
@@ -3,8 +3,8 @@
import type { AppViewerContext } from '../types'
import { Anchor, ArrowDownFromLine, Bug, Expand, Network, Pen, Plug } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Button, Popup } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { Button } from '$lib/components/common'
import type { AppComponent } from './component'
import { twMerge } from 'tailwind-merge'
import { connectOutput } from './appUtils'
@@ -73,11 +73,12 @@
style="left: {id_width}px;"
data-connection-button
>
<Popup
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-start' }}
bind:popupHover={connectingPopupHover}
closeOnOtherPopoverOpen
contentClasses="p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<AnimatedButton
animate={true}
baseRadius="9999px"
@@ -93,13 +94,15 @@
>
</AnimatedButton>
</svelte:fragment>
<ComponentOutputViewer
suffix="connect"
on:select={({ detail }) =>
connectOutput(connectingInput, component.type, component.id, detail)}
componentId={component.id}
/>
</Popup>
<svelte:fragment slot="content">
<ComponentOutputViewer
suffix="connect"
on:select={({ detail }) =>
connectOutput(connectingInput, component.type, component.id, detail)}
componentId={component.id}
/>
</svelte:fragment>
</Popover>
</div>
{/if}
@@ -278,13 +281,15 @@
'text-red-500 px-1 text-2xs py-0.5 font-bold w-fit absolute border border-red-500 -bottom-1 shadow left-1/2 transform -translate-x-1/2 z-50 cursor-pointer'
)}
>
<Popover notClickable placement="bottom" popupClass="!bg-surface border w-96">
<Bug size={14} />
<span slot="text">
<Popover notClickable placement="bottom" contentClasses="!bg-surface border w-96 p-4">
<svelte:fragment slot="trigger">
<Bug size={14} />
</svelte:fragment>
<svelte:fragment slot="content">
<div class="bg-surface">
<pre class=" whitespace-pre-wrap text-red-600 bg-surface border w-full p-4 text-xs mb-2"
<pre class="whitespace-pre-wrap text-red-600 bg-surface border w-full p-4 text-xs mb-2"
>{error ?? ''}
</pre>
</pre>
</div>
<Button
color="red"
@@ -292,7 +297,7 @@
on:click={() => $openDebugRun?.($errorByComponent[component.id]?.id ?? '')}
>Open Debug Runs</Button
>
</span>
</svelte:fragment>
</Popover>
</span>
{/if}
@@ -69,8 +69,6 @@
getCustomComponents()
</script>
<div id="cc_portal" />
<div class="p-2 flex flex-col items-start w-auto gap-2 relative">
<div class="w-full flex flex-col gap-y-2 pb-8">
<div>
@@ -1,7 +1,6 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { Popup } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { Pen } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
@@ -21,29 +20,30 @@
}
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row gap-1 items-center">
<Pen size={16} />
</div>
</Button>
</svelte:fragment>
<div class="flex flex-col w-80 gap-2">
<div class="leading-6 font-semibold text-xs">Edit {kind} name</div>
<div class="flex flex-row gap-2">
<input on:keydown={onkeydown} bind:value={editedName} />
<Button
color="dark"
size="xs"
on:click={() => dispatch('update', { path: row.path, name: editedName }) && close(null)}
>
Update
</Button>
<svelte:fragment slot="content" let:close>
<div class="flex flex-col w-80 gap-2 p-4">
<div class="leading-6 font-semibold text-xs">Edit {kind} name</div>
<div class="flex flex-row gap-2">
<input on:keydown={onkeydown} bind:value={editedName} />
<Button
color="dark"
size="xs"
on:click={() => {
dispatch('update', { path: row.path, name: editedName })
close()
}}
>
Update
</Button>
</div>
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
@@ -2,8 +2,7 @@
import { updateTheme } from './themeUtils'
import { workspaceStore } from '$lib/stores'
import Button from '$lib/components/common/button/Button.svelte'
import { Popup } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { Pen } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import { sendUserToast } from '$lib/toast'
@@ -18,19 +17,19 @@
const dispatch = createEventDispatcher()
</script>
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
closeOnOtherPopoverOpen
contentClasses="flex flex-col w-80 gap-2 p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row gap-1 items-center">
<Pen size={16} />
</div>
</Button>
</svelte:fragment>
<div class="flex flex-col w-80 gap-2">
<svelte:fragment slot="content" let:close>
<div class="leading-6 font-semibold text-xs">Edit theme name</div>
<div class="flex flex-row gap-2">
<input bind:value={editedName} />
@@ -46,12 +45,12 @@
}
})
dispatch('reloadThemes')
close(null)
close()
sendUserToast('Theme name updated:\n' + editedName)
}}
>
Update
</Button>
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
@@ -4,7 +4,7 @@
import { Pencil } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import IdEditorInput from '$lib/components/IdEditorInput.svelte'
import { Popup } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -15,8 +15,12 @@
$: reservedIds = allItems($app.grid, $app.subgrids).map((item) => item.id)
</script>
<Popup let:close floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
<svelte:fragment slot="button">
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}
closeOnOtherPopoverOpen
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
<button
on:click={() => {
$selectedComponent = [id]
@@ -28,13 +32,15 @@
<Pencil size={14} />
</button>
</svelte:fragment>
<IdEditorInput
initialId={id}
on:close={() => close(null)}
on:save={(e) => {
dispatch('save', e.detail)
close(null)
}}
{reservedIds}
/>
</Popup>
<svelte:fragment slot="content" let:close>
<IdEditorInput
initialId={id}
on:close={() => close()}
on:save={(e) => {
dispatch('save', e.detail)
close()
}}
{reservedIds}
/>
</svelte:fragment>
</Popover>
@@ -1,13 +1,14 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { Button, Popup, SecondsInput } from '$lib/components/common'
import { autoPlacement } from '@floating-ui/core'
import { Button, SecondsInput } from '$lib/components/common'
import { Database } from 'lucide-svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { autoPlacement } from '@floating-ui/core'
export let cache_ttl: number | undefined
</script>
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -15,8 +16,10 @@
})
]
}}
closeButton
contentClasses="block text-primary p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
nonCaptureEvent={true}
btnClasses={Boolean(cache_ttl)
@@ -30,7 +33,7 @@
title="Cache settings"
/>
</svelte:fragment>
<div class="block text-primary">
<svelte:fragment slot="content">
<Toggle
checked={Boolean(cache_ttl)}
on:change={() => {
@@ -53,5 +56,5 @@
<SecondsInput disabled />
{/if}
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Button, Drawer, DrawerContent, Popup } from '$lib/components/common'
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import { base } from '$lib/base'
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
@@ -23,6 +23,7 @@
import { sendUserToast } from '$lib/toast'
import { autoPlacement } from '@floating-ui/core'
import { ExternalLink, Eye, GitFork, Pen, RefreshCw, Trash } from 'lucide-svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
export let runnable: RunnableByPath
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
@@ -180,7 +181,7 @@
Fork
</Button>
{/if}
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -195,8 +196,10 @@
})
]
}}
closeButton
contentClasses="block text-primary text-xs p-4 w-[20vh]"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
nonCaptureEvent={true}
btnClasses={'bg-surface text-primay hover:bg-hover'}
@@ -205,11 +208,12 @@
size="xs">Cache</Button
>
</svelte:fragment>
<div class="block text-primary">
<svelte:fragment slot="content">
Since this is a reference to a workspace {runnable.runType}, set the cache in the {runnable.runType}
settings directly by editing it. The cache will be shared by any app or flow that uses this {runnable.runType}.
</div>
</Popup>
settings directly by editing it. The cache will be shared by any app or flow that uses this
{runnable.runType}.
</svelte:fragment>
</Popover>
<input
on:keydown|stopPropagation
@@ -6,7 +6,7 @@
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
import { getContext } from 'svelte'
import { getContext, createEventDispatcher } from 'svelte'
import type { AppViewerContext, RichConfiguration } from '../../types'
import type { InputConnection, InputType, UploadAppInput } from '../../inputType'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
@@ -46,6 +46,8 @@
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
let evalV2editor: EvalV2InputEditor
function applyConnection(connection: InputConnection) {
const expr = `${connection.componentId}.${connection.path}`
@@ -71,6 +73,7 @@
}
function closeConnection() {
dispatch('closeConnection')
$connectingInput = {
opened: false,
hoveredComponent: undefined,
@@ -80,6 +83,7 @@
}
function openConnection() {
dispatch('openConnection')
$connectingInput = {
opened: true,
input: undefined,
@@ -1,8 +1,9 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { ClearableInput, Popup } from '../../../../common'
import { ClearableInput } from '../../../../common'
import { AllIcons } from './icons'
import type { ComputeConfig } from 'svelte-floating-ui'
import Popover from '$lib/components/meltComponents/Popover.svelte'
export let value: string | undefined = ''
@@ -40,11 +41,10 @@
strategy: 'absolute',
placement: 'bottom-end'
}
export let shouldUsePortal: boolean = true
</script>
<Popup let:close {floatingConfig} {shouldUsePortal}>
<svelte:fragment slot="button">
<Popover {floatingConfig} closeOnOtherPopoverOpen contentClasses="p-4">
<svelte:fragment slot="trigger">
<div class="relative">
<ClearableInput
readonly
@@ -60,52 +60,54 @@
{/if}
</div>
</svelte:fragment>
{#if !loading}
{#if filteredItems}
<div class="w-72">
<input
on:keydown={(event) => {
if (!['ArrowDown', 'ArrowUp'].includes(event.key)) {
event.stopPropagation()
}
}}
bind:value={search}
type="text"
placeholder="Search"
class="col-span-4 mb-2"
/>
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
{#each filteredItems as label}
<button
type="button"
title={label}
on:click={() => {
select(label)
close(null)
}}
class="w-full center-center flex-col font-normal p-1
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200 dark:hover:bg-frost-900 dark:focus:bg-frost-900
{label === value ? 'text-blue-600 bg-blue-50 pointer-events-none' : ''}"
>
<!-- svelte-ignore a11y-missing-attribute -->
<img
class="dark:invert"
loading="lazy"
src="https://cdn.jsdelivr.net/npm/lucide-static@0.367.0/icons/{label}.svg"
/>
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
{label}
</span>
</button>
{:else}
<div class="col-span-4 text-center text-secondary text-sm p-2">
No icons match your search
</div>
{/each}
<svelte:fragment slot="content" let:close>
{#if !loading}
{#if filteredItems}
<div class="w-72">
<input
on:keydown={(event) => {
if (!['ArrowDown', 'ArrowUp'].includes(event.key)) {
event.stopPropagation()
}
}}
bind:value={search}
type="text"
placeholder="Search"
class="col-span-4 mb-2"
/>
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
{#each filteredItems as label}
<button
type="button"
title={label}
on:click={() => {
select(label)
close()
}}
class="w-full center-center flex-col font-normal p-1
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200 dark:hover:bg-frost-900 dark:focus:bg-frost-900
{label === value ? 'text-blue-600 bg-blue-50 pointer-events-none' : ''}"
>
<!-- svelte-ignore a11y-missing-attribute -->
<img
class="dark:invert"
loading="lazy"
src="https://cdn.jsdelivr.net/npm/lucide-static@0.367.0/icons/{label}.svg"
/>
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
{label}
</span>
</button>
{:else}
<div class="col-span-4 text-center text-secondary text-sm p-2">
No icons match your search
</div>
{/each}
</div>
</div>
</div>
{:else}
<div class="text-center text-sm text-secondary p-2"> Couldn't load options </div>
{:else}
<div class="text-center text-sm text-secondary p-2"> Couldn't load options </div>
{/if}
{/if}
{/if}
</Popup>
</svelte:fragment>
</Popover>
@@ -1,18 +1,19 @@
<script lang="ts">
import { Filter } from 'lucide-svelte'
import { Popup, Button } from '../common'
import { Button } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
</script>
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 pt-8 bg-surface"
contentClasses="border rounded-lg shadow-lg p-4 flex flex-col w-80 pt-8 bg-surface"
>
<svelte:fragment slot="button">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Filter }}>
Filters
</Button>
</svelte:fragment>
<div class="flex flex-col w-80">
<svelte:fragment slot="content">
<slot name="filters" />
</div>
</Popup>
</svelte:fragment>
</Popover>
@@ -1,7 +1,7 @@
<script lang="ts">
import { Calendar } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import { Popup } from '..'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import type { Placement } from '@floating-ui/core'
import DateTimeInput from '$lib/components/DateTimeInput.svelte'
@@ -17,8 +17,12 @@
export let placement: Placement = 'top-end'
</script>
<Popup floatingConfig={{ placement: placement, strategy: 'absolute' }} {target}>
<svelte:fragment slot="button">
<Popover
floatingConfig={{ placement: placement, strategy: 'absolute' }}
portal={target}
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
<button
title="Open calendar picker"
class="absolute bottom-1 right-2 top-1 py-1 min-w-min !px-2 items-center text-primary bg-surface border rounded center-center hover:bg-surface-hover transition-all cursor-pointer"
@@ -30,26 +34,27 @@
<Calendar size={14} />
</button>
</svelte:fragment>
<!-- svelte-ignore a11y-label-has-associated-control -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<label class="block text-primary">
<div class="pb-1 text-sm text-secondary">{label}</div>
<svelte:fragment slot="content">
<!-- svelte-ignore a11y-label-has-associated-control -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:click|stopPropagation class="flex w-full">
<DateTimeInput
{clearable}
{useDropdown}
value={date}
on:change={(e) => {
date = e.detail
dispatch('change', date)
}}
on:clear={() => {
dispatch('clear')
}}
/>
</div>
</label>
</Popup>
<label class="block text-primary">
<div class="pb-1 text-sm text-secondary">{label}</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:click|stopPropagation class="flex w-full">
<DateTimeInput
{clearable}
{useDropdown}
value={date}
on:change={(e) => {
date = e.detail
dispatch('change', date)
}}
on:clear={() => {
dispatch('clear')
}}
/>
</div>
</label>
</svelte:fragment>
</Popover>
@@ -9,7 +9,6 @@ export { default as DrawerContent } from './drawer/DrawerContent.svelte'
export { default as Kbd } from './kbd/Kbd.svelte'
export { default as Menu } from './menu/Menu.svelte'
export { default as MenuItem } from './menu/MenuItem.svelte'
export { default as Popup } from './popup/Popup.svelte'
export { default as SecondsInput } from './seconds/SecondsInput.svelte'
export { default as Skeleton } from './skeleton/Skeleton.svelte'
export { default as Tab } from './tabs/Tab.svelte'
@@ -1,54 +0,0 @@
<script lang="ts">
import { Menu, MenuButton, MenuItems, Transition } from '@rgossiaux/svelte-headlessui'
import Portal from '$lib/components/Portal.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import { createFloatingActions } from 'svelte-floating-ui'
import { twMerge } from 'tailwind-merge'
import ResolveOpen from './ResolveOpen.svelte'
export let placement: any = 'bottom-start'
export let justifyEnd: boolean = false
export let lightMode: boolean = false
export let maxHeight: number = 900
export let disabled = false
const [floatingRef, floatingContent] = createFloatingActions({
strategy: 'fixed',
middleware: [offset(), flip(), shift()],
placement: placement
})
</script>
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
<ResolveOpen {open} on:open on:close />
<div use:floatingRef class="w-full">
<MenuButton class={twMerge('w-full', justifyEnd ? 'flex justify-end' : '')} disabled={disabled}>
<slot name="trigger" />
</MenuButton>
</div>
<Portal name="menu-v2">
<div use:floatingContent class="z-[6000]">
<Transition
{open}
enter="transition ease-out duration-[25ms]"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-[25ms]"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems
class={twMerge(
'border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto',
lightMode ? 'bg-surface-inverse' : 'bg-surface'
)}
style="max-height: {maxHeight}px;"
>
<div class="my-1">
<slot />
</div>
</MenuItems>
</Transition>
</div>
</Portal>
</Menu>
@@ -1,70 +0,0 @@
<script lang="ts">
import { Popover, PopoverButton, PopoverPanel, Transition } from '@rgossiaux/svelte-headlessui'
import ConditionalPortal from '../drawer/ConditionalPortal.svelte'
import { createFloatingActions, type ComputeConfig } from 'svelte-floating-ui'
export let floatingConfig: ComputeConfig = {
strategy: 'absolute',
placement: 'bottom-start'
}
export let containerClasses: string = 'rounded-lg shadow-md border p-4 bg-surface'
export let floatingClasses: string = ''
const [floatingRef, floatingContent] = createFloatingActions(floatingConfig)
export let blockOpen = false
export let shouldUsePortal: boolean = true
export let target: string | HTMLElement | undefined = undefined
export let noTransition = false
export let popupHover = false
export let preventPopupClosingOnClickInside = false
export let disabled = false
</script>
<Popover on:close class="leading-none">
<PopoverButton let:open {disabled}>
<div use:floatingRef>
<slot name="button" {open} />
</div>
</PopoverButton>
<ConditionalPortal condition={shouldUsePortal} {target}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
use:floatingContent
class={`z5000 ${floatingClasses}`}
on:mouseenter={() => {
popupHover = true
}}
on:mouseleave={() => {
popupHover = false
}}
on:click={(e) => preventPopupClosingOnClickInside && e.stopPropagation()}
>
{#if !noTransition}
<Transition
show={blockOpen || undefined}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<PopoverPanel let:close static={blockOpen}>
<div class={containerClasses}>
<slot {close} />
</div>
</PopoverPanel>
</Transition>
{:else}
<PopoverPanel focus={false} let:close static={blockOpen}>
<div class={containerClasses}>
<slot {close} />
</div>
</PopoverPanel>
{/if}
</div>
</ConditionalPortal>
</Popover>
@@ -2,7 +2,7 @@
import { ExternalLink, Wand2 } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { getNonStreamingCompletion } from './lib'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { sendUserToast } from '$lib/toast'
import { copilotInfo } from '$lib/stores'
@@ -74,12 +74,8 @@
}
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
<svelte:fragment slot="trigger">
<Button
color={genLoading ? 'red' : 'light'}
size="xs"
@@ -93,47 +89,51 @@
on:click={genLoading ? () => abortController?.abort() : undefined}
/>
</svelte:fragment>
{#if $copilotInfo.exists_ai_resource}
<div class="flex w-96">
<input
bind:this={instructionsField}
type="text"
placeholder="CRON schedule description"
bind:value={instructions}
on:keypress={({ key }) => {
if (key === 'Enter' && instructions.length > 0) {
close(instructionsField || null)
generateCron()
}
}}
/>
<Button
size="xs"
color="light"
variant="contained"
buttonType="button"
btnClasses="!ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate CRON schedule from prompt"
aria-label="Generate"
iconOnly
on:click={() => {
close(instructionsField || null)
generateCron()
}}
disabled={instructions.length == 0}
startIcon={{ icon: Wand2 }}
/>
<svelte:fragment slot="content" let:close>
<div class="border rounded-lg shadow-lg p-4 bg-surface">
{#if $copilotInfo.exists_ai_resource}
<div class="flex w-96">
<input
bind:this={instructionsField}
type="text"
placeholder="CRON schedule description"
bind:value={instructions}
on:keypress={({ key }) => {
if (key === 'Enter' && instructions.length > 0) {
close()
generateCron()
}
}}
/>
<Button
size="xs"
color="light"
variant="contained"
buttonType="button"
btnClasses="!ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate CRON schedule from prompt"
aria-label="Generate"
iconOnly
on:click={() => {
close()
generateCron()
}}
disabled={instructions.length == 0}
startIcon={{ icon: Wand2 }}
/>
</div>
{:else}
<div class="block text-primary">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
></p
>
</div>
{/if}
</div>
{:else}
<div class="block text-primary">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
></p
>
</div>
{/if}
</Popup>
</svelte:fragment>
</Popover>
@@ -7,7 +7,7 @@
import type { FlowCopilotContext } from './flow'
import { charsToNumber } from '../flows/idUtils'
import { copilotInfo } from '$lib/stores'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import type { FlowModule } from '$lib/gen'
import type { FlowEditorContext } from '../flows/types'
import { ExternalLink, Wand2 } from 'lucide-svelte'
@@ -114,13 +114,8 @@
</div>
</ManualPopover>
{:else}
<Popup
floatingConfig={{
strategy: 'absolute',
placement: 'bottom'
}}
>
<svelte:fragment slot="button">
<Popover placement="bottom">
<svelte:fragment slot="trigger">
<Button
size="xs"
startIcon={{
@@ -134,15 +129,17 @@
AI builder
</Button>
</svelte:fragment>
<div class="block text-primary">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
></p
>
</div>
</Popup>
<svelte:fragment slot="content">
<div class="block text-primary p-4">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
></p
>
</div>
</svelte:fragment>
</Popover>
{/if}
@@ -11,7 +11,7 @@
import { dfs } from '../flows/dfs'
import { yamlStringifyExceptKeys } from './utils'
import { copilotInfo, stepInputCompletionEnabled } from '$lib/stores'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import type { Flow } from '$lib/gen'
let loading = false
@@ -84,12 +84,11 @@ Only return the expression without any wrapper. Do not explain or discuss.`
</script>
{#if $copilotInfo.exists_ai_resource && $stepInputCompletionEnabled}
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
contentClasses="p-4 flex w-96"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
color={loading ? 'red' : 'light'}
size="xs"
@@ -103,7 +102,7 @@ Only return the expression without any wrapper. Do not explain or discuss.`
on:click={loading ? () => abortController?.abort() : undefined}
/>
</svelte:fragment>
<div class="flex w-96">
<svelte:fragment slot="content" let:close>
<input
bind:this={instructionsField}
type="text"
@@ -111,7 +110,7 @@ Only return the expression without any wrapper. Do not explain or discuss.`
bind:value={instructions}
on:keypress={({ key }) => {
if (key === 'Enter' && instructions.length > 0) {
close(instructionsField || null)
close()
generatePredicate()
}
}}
@@ -126,12 +125,12 @@ Only return the expression without any wrapper. Do not explain or discuss.`
aria-label="Generate"
iconOnly
on:click={() => {
close(instructionsField || null)
close()
generatePredicate()
}}
disabled={instructions.length == 0}
startIcon={{ icon: Wand2 }}
/>
</div>
</Popup>
</svelte:fragment>
</Popover>
{/if}
@@ -4,7 +4,7 @@
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { copilotInfo } from '$lib/stores'
import { autoPlacement } from '@floating-ui/core'
@@ -91,7 +91,7 @@
}
</script>
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -99,9 +99,8 @@
})
]
}}
let:close
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
title="Generate regexes from prompt"
btnClasses="text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
@@ -115,67 +114,69 @@
on:click={genLoading ? () => abortController?.abort() : () => {}}
/>
</svelte:fragment>
<div class="block text-primary z-[2000]">
{#if $copilotInfo.exists_ai_resource}
<div class="flex flex-col gap-4">
<div class="flex w-96">
<input
type="text"
bind:this={input}
bind:value={funcDesc}
on:keypress={({ key }) => {
if (key === 'Enter' && funcDesc?.length > 0) {
close(input || null)
<svelte:fragment slot="content" let:close>
<div class="block text-primary p-4">
{#if $copilotInfo.exists_ai_resource}
<div class="flex flex-col gap-4">
<div class="flex w-96">
<input
type="text"
bind:this={input}
bind:value={funcDesc}
on:keypress={({ key }) => {
if (key === 'Enter' && funcDesc?.length > 0) {
close()
onGenerate()
}
}}
placeholder={'Describe what the regex should doww'}
/>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
aria-label="Generate"
on:click={() => {
close()
onGenerate()
}
}}
placeholder={'Describe what the regex should do'}
/>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
aria-label="Generate"
on:click={() => {
close(input || null)
onGenerate()
}}
disabled={funcDesc?.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>
</div>
{#if promptHistory.length > 0}
<div class="w-96 flex flex-col gap-1">
{#each promptHistory as p}
<Button
size="xs2"
color="light"
btnClasses="justify-start overflow-x-scroll no-scrollbar"
startIcon={{ icon: HistoryIcon, classes: 'shrink-0' }}
on:click={() => {
funcDesc = p
}}>{p}</Button
>
{/each}
<button
class="underline text-xs text-start px-2 text-secondary font-normal"
on:click={clearPromptHistory}>clear history</button
>
}}
disabled={funcDesc?.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>
</div>
{/if}
</div>
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
</p>
{/if}
</div>
</Popup>
{#if promptHistory.length > 0}
<div class="w-96 flex flex-col gap-1">
{#each promptHistory as p}
<Button
size="xs2"
color="light"
btnClasses="justify-start overflow-x-scroll no-scrollbar"
startIcon={{ icon: HistoryIcon, classes: 'shrink-0' }}
on:click={() => {
funcDesc = p
}}>{p}</Button
>
{/each}
<button
class="underline text-xs text-start px-2 text-secondary font-normal"
on:click={clearPromptHistory}>clear history</button
>
</div>
{/if}
</div>
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
</p>
{/if}
</div>
</svelte:fragment>
</Popover>
@@ -9,15 +9,13 @@
import { dbSchemas, copilotInfo, type DBSchema, workspaceStore } from '$lib/stores'
import type DiffEditor from '../DiffEditor.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
import Popover from '../Popover.svelte'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { writable } from 'svelte/store'
import { sleep } from '$lib/utils'
import { WindmillIcon } from '../icons'
import HighlightCode from '../HighlightCode.svelte'
import LoadingIcon from '../apps/svelte-select/lib/LoadingIcon.svelte'
import { autoPlacement } from '@floating-ui/core'
import { Check, Wand2, X } from 'lucide-svelte'
import { Check, Wand2, X, RotateCw } from 'lucide-svelte'
// props
export let lang: SupportedLanguage
@@ -33,7 +31,7 @@
let dbSchema: DBSchema | undefined = undefined
let abortController: AbortController | undefined = undefined
async function onFix(closePopup: () => void) {
async function onFix() {
if (!error) {
return
}
@@ -57,9 +55,6 @@
)
setupDiff()
diffEditor?.setModified($generatedCode)
await sleep(500)
closePopup()
await sleep(300)
showDiff()
} catch (err) {
if (!abortController?.signal.aborted) {
@@ -70,7 +65,6 @@
console.error(err)
}
}
closePopup()
} finally {
genLoading = false
}
@@ -117,110 +111,134 @@
}
}
$: updateSchema(lang, args)
let popover: Popover | undefined = undefined
</script>
{#if SUPPORTED_LANGUAGES.has(lang)}
<div>
{#if !genLoading && $generatedCode.length > 0}
<div class="flex gap-1">
{#if !genLoading && $generatedCode.length > 0}
<div class="flex gap-1">
<Button
title="Discard generated code"
size="xs"
color="red"
spacingSize="xs2"
on:click={() => {
popover?.close()
rejectDiff()
}}
variant="contained"
startIcon={{ icon: X }}
propagateEvent={true}
>
Discard
</Button>
<Button
title="Accept generated code"
size="xs"
color="green"
spacingSize="xs2"
on:click={() => {
popover?.close()
acceptDiff()
}}
startIcon={{ icon: Check }}
propagateEvent={true}
>
Accept
</Button>
<Button
size="xs"
color="light"
spacingSize="xs2"
on:click={() => {
$generatedExplanation = ''
popover?.open()
onFix()
}}
startIcon={{ icon: RotateCw }}
title="Retry"
btnClasses="text-violet-800 dark:text-violet-400"
/>
</div>
{/if}
<Popover
bind:this={popover}
floatingConfig={{
middleware: [
autoPlacement({
allowedPlacements: ['bottom-end', 'top-end']
})
]
}}
closeOnOutsideClick={!genLoading}
closeButton={!genLoading}
displayArrow={true}
>
<svelte:fragment slot="trigger" let:isOpen>
<div class="flex flex-row">
<Button
title="Discard generated code"
title="Fix code"
size="xs"
color="red"
color={genLoading ? 'red' : 'light'}
spacingSize="xs2"
on:click={rejectDiff}
variant="contained"
startIcon={{ icon: X }}
startIcon={genLoading ? undefined : { icon: Wand2 }}
propagateEvent={true}
on:click={genLoading
? () => abortController?.abort()
: $generatedCode.length > 0
? undefined
: () => onFix()}
btnClasses={genLoading
? ''
: 'text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700 min-w-[84px]'}
>
Discard
</Button><Button
title="Accept generated code"
size="xs"
color="green"
spacingSize="xs2"
on:click={acceptDiff}
startIcon={{ icon: Check }}
>
Accept
{#if genLoading}
<WindmillIcon
white
class="mr-1 text-white"
height="16px"
width="20px"
spin="veryfast"
/>
Stop
{:else if $generatedCode.length > 0}
<span class="text-xs">{isOpen ? 'Hide' : 'Show'}</span>
{:else}
AI Fix
{/if}
</Button>
{#if $generatedExplanation.length > 0}
<Popover>
<svelte:fragment slot="text">{$generatedExplanation}</svelte:fragment>
<Button size="xs" color="light" variant="contained" spacingSize="xs2">Explain</Button>
</Popover>
</div>
</svelte:fragment>
<svelte:fragment slot="content">
<div class="p-4">
{#if $copilotInfo.exists_ai_resource}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-auto">
{#if $generatedCode.length > 0 && genLoading}
<div class="overflow-x-scroll">
<HighlightCode language={lang} code={$generatedCode} />
</div>
{:else if genLoading}
<LoadingIcon />
{/if}
{#if $generatedExplanation.length > 0}
<p class="text-sm mt-2"
><span class="font-bold">Explanation: test</span> {$generatedExplanation}</p
>
{/if}
</div>
{:else}
<div class="w-80">
<p class="text-sm"
>Enable Windmill AI in the <a
class="inline-flex flex-row items-center gap-1"
href="{base}/workspace_settings?tab=ai"
target="_blank">workspace settings</a
></p
></div
>
{/if}
</div>
{:else}
<Popup
floatingConfig={{
middleware: [
autoPlacement({
allowedPlacements: ['bottom-end', 'top-end']
})
]
}}
let:close
>
<svelte:fragment slot="button">
<Button
title="Fix code"
size="xs"
color={genLoading ? 'red' : 'light'}
spacingSize="xs2"
startIcon={genLoading ? undefined : { icon: Wand2 }}
nonCaptureEvent={!genLoading}
on:click={genLoading ? () => abortController?.abort() : undefined}
btnClasses={genLoading
? ''
: 'text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700'}
>{#if genLoading}
<WindmillIcon
white
class="mr-1 text-white"
height="16px"
width="20px"
spin="veryfast"
/>
Stop
{:else}
AI Fix
{/if}
</Button>
</svelte:fragment>
{@const fixAction = (_) => {
if ($copilotInfo.exists_ai_resource) {
onFix(() => close(null))
}
}}
<div use:fixAction>
{#if $copilotInfo.exists_ai_resource}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-scroll">
{#if $generatedCode.length > 0}
<div class="overflow-x-scroll">
<HighlightCode language={lang} code={$generatedCode} />
</div>
{#if $generatedExplanation.length > 0}
<p class="text-sm mt-2"
><span class="font-bold">Explanation:</span> {$generatedExplanation}</p
>
{/if}
{:else}
<LoadingIcon />
{/if}
</div>
{:else}
<div class="w-80">
<p class="text-sm"
>Enable Windmill AI in the <a
class="inline-flex flex-row items-center gap-1"
href="{base}/workspace_settings?tab=ai"
target="_blank">workspace settings</a
></p
></div
>
{/if}
</div>
</Popup>
{/if}
</div>
</svelte:fragment>
</Popover>
{/if}
@@ -6,7 +6,8 @@
import type { SupportedLanguage } from '$lib/common'
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import TooltipV2 from '$lib/components/meltComponents/Tooltip.svelte'
import {
dbSchemas,
copilotInfo,
@@ -29,7 +30,6 @@
import { fade } from 'svelte/transition'
import { isInitialCode } from '$lib/script_helpers'
import { twMerge } from 'tailwind-merge'
import Popover from '../Popover.svelte'
import { onDestroy } from 'svelte'
// props
@@ -249,6 +249,11 @@
}
$: $copilotInfo && checkForInvalidModel()
function handlePublicOnlySelected({ detail }: { detail: string }) {
if (!dbSchema) return
;(dbSchema as any).publicOnly = detail === 'true'
}
onDestroy(() => {
abortController?.abort()
})
@@ -311,7 +316,7 @@
{/if}
{/if}
{#if ($generatedCode.length === 0 || genLoading) && SUPPORTED_LANGUAGES.has(lang ?? '')}
<Popup
<Popover
floatingConfig={{
middleware: [
autoPlacement({
@@ -319,10 +324,9 @@
})
]
}}
let:close
blockOpen={blockPopupOpen}
disabled={blockPopupOpen}
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
{#if inlineScript}
<Button
size="xs"
@@ -387,143 +391,151 @@
</Button>
{/if}
</svelte:fragment>
<div class="block text-primary">
{#if genLoading}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-scroll" bind:this={codeDiv}>
{#if $generatedCode.length > 0}
<div class="overflow-x-scroll">
<HighlightCode language={lang} code={$generatedCode} /></div
>
{:else}
<LoadingIcon />
{/if}
</div>
{: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" bind:selected={mode}>
<ToggleButton value={'gen'} label="Generate from scratch" small light />
<ToggleButton value={'edit'} label="Edit existing code" small light />
</ToggleButtonGroup>
<div class="min-w-0">
{#if $copilotInfo.ai_models.length > 1}
<select
bind:value={$copilotSessionModel}
class="!text-xs !pr-5 !bg-[right_center] overflow-ellipsis text-right !border-none !shadow-none"
>
{#each $copilotInfo.ai_models as model}
<option value={model} class="pr-4">{model}</option>
{/each}
</select>
{:else if $copilotInfo.ai_models.length === 1}
<div class="text-xs whitespace-nowrap overflow-hidden overflow-ellipsis">
{$copilotInfo.ai_models[0]}
</div>
{/if}
</div>
</div>
<div class="flex w-96 items-start">
<textarea
bind:this={input}
bind:value={funcDesc}
on:input={autoResize}
on:keydown={({ key, shiftKey }) => {
if (key === 'Enter' && !shiftKey && trimmedDesc.length > 0) {
onGenerate(() => close(input || null))
return false
}
}}
placeholder={mode === 'edit'
? 'Describe the changes you want'
: 'Describe what the script should do'}
rows="1"
class="resize-none overflow-hidden"
/>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!h-[34px] qhd:!h-[38px] !ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate code from prompt"
aria-label="Generate"
on:click={() => {
onGenerate(() => close(input || null))
}}
disabled={trimmedDesc.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>
</div>
{#if promptHistory.length > 0}
<div class="w-96 flex flex-col gap-1">
{#each promptHistory as p}
<Button
size="xs2"
color="light"
btnClasses="justify-start overflow-x-scroll no-scrollbar"
startIcon={{ icon: HistoryIcon, classes: 'shrink-0' }}
on:click={() => {
funcDesc = p
setTimeout(() => {
autoResize()
}, 0)
}}>{p}</Button
>
{/each}
<button
class="underline text-xs text-start px-2 text-secondary font-normal"
on:click={clearPromptHistory}>clear history</button
<svelte:fragment slot="content" let:close>
<div class="p-4">
{#if genLoading}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-scroll" bind:this={codeDiv}>
{#if $generatedCode.length > 0}
<div class="overflow-x-scroll">
<HighlightCode language={lang} code={$generatedCode} /></div
>
</div>
{/if}
{:else}
<LoadingIcon />
{/if}
</div>
{: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>
{#if ['postgresql', 'mysql', 'snowflake', 'bigquery', 'mssql', 'graphql, oracledb'].includes(lang ?? '') && dbSchema?.lang === lang}
<div class="flex flex-row items-center justify-between gap-2 w-96">
<div class="flex flex-row items-center gap-1">
<p class="text-xs text-secondary">
Context: {lang === 'graphql' ? 'GraphQL' : 'DB'} schema
</p>
<Tooltip placement="top">
We pass the selected schema to GPT-4 Turbo for better script generation.
</Tooltip>
{#if dbSchema && dbSchema.stringified.length > MAX_SCHEMA_LENGTH}
<Popover notClickable placement="top">
<AlertTriangle size={16} class="text-yellow-500" />
<svelte:fragment slot="text">
The schema is about {addThousandsSeparator(dbSchema.stringified.length / 3.5)}
tokens. To avoid exceeding the model's context length, it will be truncated to
{addThousandsSeparator(MAX_SCHEMA_LENGTH / 3.5)}
tokens.
</svelte:fragment>
</Popover>
<div class="min-w-0">
{#if $copilotInfo.ai_models.length > 1}
<select
bind:value={$copilotSessionModel}
class="!text-xs !pr-5 !bg-[right_center] overflow-ellipsis text-right !border-none !shadow-none"
>
{#each $copilotInfo.ai_models as model}
<option value={model} class="pr-4">{model}</option>
{/each}
</select>
{:else if $copilotInfo.ai_models.length === 1}
<div class="text-xs whitespace-nowrap overflow-hidden overflow-ellipsis">
{$copilotInfo.ai_models[0]}
</div>
{/if}
</div>
{#if dbSchema && dbSchema.lang !== 'graphql' && (dbSchema.schema?.public || dbSchema.schema?.PUBLIC || dbSchema.schema?.dbo)}
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={dbSchema.publicOnly}>
<ToggleButton
value={true}
label={(dbSchema.schema?.dbo ? 'Dbo' : 'Public') + ' schema'}
small
light
/>
<ToggleButton value={false} label="All schemas" small light />
</ToggleButtonGroup>
{/if}
</div>
{/if}
</div>
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
</p>
{/if}
</div>
</Popup>
<div class="flex w-96 items-start">
<textarea
bind:this={input}
bind:value={funcDesc}
on:input={autoResize}
on:keydown={({ key, shiftKey }) => {
if (key === 'Enter' && !shiftKey && trimmedDesc.length > 0) {
onGenerate(() => close())
return false
}
}}
placeholder={mode === 'edit'
? 'Describe the changes you want'
: 'Describe what the script should do'}
rows="1"
class="resize-none overflow-hidden"
/>
<Button
size="xs"
color="light"
buttonType="button"
btnClasses="!h-[34px] qhd:!h-[38px] !ml-2 text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700"
title="Generate code from prompt"
aria-label="Generate"
on:click={() => {
onGenerate(() => close())
}}
disabled={trimmedDesc.length <= 0}
iconOnly
startIcon={{ icon: Wand2 }}
/>
</div>
{#if promptHistory.length > 0}
<div class="w-96 flex flex-col gap-1">
{#each promptHistory as p}
<Button
size="xs2"
color="light"
btnClasses="justify-start overflow-x-scroll no-scrollbar"
startIcon={{ icon: HistoryIcon, classes: 'shrink-0' }}
on:click={() => {
funcDesc = p
setTimeout(() => {
autoResize()
}, 0)
}}>{p}</Button
>
{/each}
<button
class="underline text-xs text-start px-2 text-secondary font-normal"
on:click={clearPromptHistory}>clear history</button
>
</div>
{/if}
{#if ['postgresql', 'mysql', 'snowflake', 'bigquery', 'mssql', 'graphql, oracledb'].includes(lang ?? '') && dbSchema?.lang === lang}
<div class="flex flex-row items-center justify-between gap-2 w-96">
<div class="flex flex-row items-center gap-1">
<p class="text-xs text-secondary">
Context: {lang === 'graphql' ? 'GraphQL' : 'DB'} schema
</p>
<Tooltip placement="top">
We pass the selected schema to GPT-4 Turbo for better script generation.
</Tooltip>
{#if dbSchema && dbSchema.stringified.length > MAX_SCHEMA_LENGTH}
<TooltipV2 notClickable placement="top">
<AlertTriangle size={16} class="text-yellow-500" />
<svelte:fragment slot="text">
The schema is about {addThousandsSeparator(
dbSchema.stringified.length / 3.5
)}
tokens. To avoid exceeding the model's context length, it will be truncated to
{addThousandsSeparator(MAX_SCHEMA_LENGTH / 3.5)}
tokens.
</svelte:fragment>
</TooltipV2>
{/if}
</div>
{#if dbSchema && dbSchema.lang !== 'graphql' && (dbSchema.schema?.public || dbSchema.schema?.PUBLIC || dbSchema.schema?.dbo)}
<ToggleButtonGroup
class="w-auto shrink-0"
selected={dbSchema?.publicOnly ? 'true' : 'false'}
on:selected={handlePublicOnlySelected}
>
<ToggleButton
value={'true'}
label={(dbSchema.schema?.dbo ? 'Dbo' : 'Public') + ' schema'}
small
light
/>
<ToggleButton value={'false'} label="All schemas" small light />
</ToggleButtonGroup>
{/if}
</div>
{/if}
</div>
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
</p>
{/if}
</div>
</svelte:fragment>
</Popover>
{/if}
@@ -13,7 +13,7 @@
import type { FlowCopilotContext } from './flow'
import { Check, ExternalLink, Loader2, Wand2 } from 'lucide-svelte'
import { copilotInfo, stepInputCompletionEnabled } from '$lib/stores'
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import type { SchemaProperty, Schema } from '$lib/common'
import FlowCopilotInputsModal from './FlowCopilotInputsModal.svelte'
import type { Flow } from '$lib/gen'
@@ -217,13 +217,12 @@ input_name2: expression2
{/if}
</Button>
{:else}
<Popup
<Popover
floatingConfig={{
placement: 'top-end'
}}
let:close
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
size="xs"
color="light"
@@ -236,29 +235,33 @@ input_name2: expression2
Fill inputs
</Button>
</svelte:fragment>
<p class="text-sm">
{#if !$copilotInfo.exists_ai_resource}
Enable Windmill AI in the{' '}
<a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
{:else}
Enable step input completion in the{' '}
<a
href="#user-settings"
class="inline-flex flex-row items-center gap-1"
on:click={() => {
close(null)
}}
>
user settings
</a>
{/if}
</p>
</Popup>
<svelte:fragment slot="content" let:close>
<div class="p-4">
<p class="text-sm">
{#if !$copilotInfo.exists_ai_resource}
Enable Windmill AI in the{' '}
<a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
workspace settings <ExternalLink size={16} />
</a>
{:else}
Enable step input completion in the{' '}
<a
href="#user-settings"
class="inline-flex flex-row items-center gap-1"
on:click={() => {
close()
}}
>
user settings
</a>
{/if}
</p>
</div>
</svelte:fragment>
</Popover>
{/if}
</div>
@@ -1,59 +0,0 @@
<script lang="ts">
import Menu from '$lib/components/common/menu/Menu.svelte'
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import Popover from '$lib/components/Popover.svelte'
import { getContext } from 'svelte'
import { copilotInfo } from '$lib/stores'
import { ExternalLink, Wand2 } from 'lucide-svelte'
import { base } from '$lib/base'
let openNoCopilot = false
export let className = ''
const { drawerStore: copilotDrawerStore } =
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
</script>
<div class={className}>
<Popover>
<div class={openNoCopilot ? 'z-10' : ''}>
<Menu pointerDown noMinW placement="bottom-center" let:close bind:show={openNoCopilot}>
<button
title="AI Flow Builder"
on:pointerdown={$copilotInfo.exists_ai_resource
? (ev) => {
ev.preventDefault()
ev.stopPropagation()
$copilotDrawerStore?.openDrawer()
}
: undefined}
slot="trigger"
type="button"
class=" bg-surface text-violet-800 dark:text-violet-400 border mx-0.5 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-7 h-7 flex items-center justify-center"
>
<Wand2 size={12} />
</button>
{#if !$copilotInfo.exists_ai_resource}
<div class="text-primary p-4">
<p class="text-sm w-80">
Enable Windmill AI in the
<a
href="{base}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
on:click={() => {
close()
}}
>
workspace settings
<ExternalLink size={16} />
</a>
</p>
</div>
{/if}
</Menu>
</div>
<svelte:fragment slot="text">AI Flow builder</svelte:fragment>
</Popover>
</div>
@@ -1,12 +1,13 @@
<script lang="ts">
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
import { Popup } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
import { Plug } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { PropPickerContext } from '$lib/components/prop_picker'
import { getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { tick } from 'svelte'
export let json = {}
@@ -31,16 +32,20 @@
}}
on:keydown|preventDefault|stopPropagation
data-prop-picker
title=""
>
<AnimatedButton
animate={isConnecting}
wrapperClasses="h-[20px] w-[20px] "
wrapperClasses="h-[20px] w-[20px] center-center"
baseRadius="9999px"
marginWidth="1px"
>
<Popup floatingConfig={{ strategy: 'fixed', placement: 'bottom-start' }}>
<svelte:fragment slot="button" let:open>
<Popover disablePopup={open}>
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-start' }}
closeOnOtherPopoverOpen={true}
>
<svelte:fragment slot="trigger" let:isOpen>
<Tooltip disablePopup={isOpen}>
<svelte:fragment slot="text">node outputs</svelte:fragment>
<button
class={twMerge(
@@ -52,25 +57,24 @@
>
<Plug size={12} strokeWidth={2} />
</button>
</Popover>
</Tooltip>
</svelte:fragment>
<div data-prop-picker>
<ObjectViewer
{json}
topBrackets={false}
pureViewer={false}
{prefix}
on:select={({ detail }) => {
if ($flowPropPickerConfig?.onSelect(detail)) {
$flowPropPickerConfig?.clearFocus()
// console.log('BAR')
} else {
// console.log('FOO')
}
}}
allowCopy={!$flowPropPickerConfig}
/>
</div>
</Popup>
<svelte:fragment slot="content">
<div class="p-4 max-h-[50vh] overflow-y-auto" data-prop-picker>
<ObjectViewer
{json}
topBrackets={false}
pureViewer={false}
{prefix}
on:select={({ detail }) => {
if ($flowPropPickerConfig?.onSelect(detail)) {
$flowPropPickerConfig?.clearFocus()
}
}}
allowCopy={!$flowPropPickerConfig}
/>
</div>
</svelte:fragment>
</Popover>
</AnimatedButton>
</button>
@@ -42,7 +42,7 @@
import DrawerContent from '../common/drawer/DrawerContent.svelte'
import Item from './Item.svelte'
import TreeViewRoot from './TreeViewRoot.svelte'
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { getContext } from 'svelte'
type TableItem<T, U extends 'script' | 'flow' | 'app' | 'raw_app'> = T & {
@@ -187,7 +187,7 @@
ownerFilter != undefined
? combinedItems?.filter(
(x) =>
x.path.startsWith(ownerFilter + '/' ?? '') &&
x.path.startsWith(ownerFilter + '/') &&
(x.type == itemKind || itemKind == 'all') &&
filterItemsPathsBaseOnUserFilters(x, filterUserFolders)
)
@@ -289,7 +289,9 @@
$: storeLocalSetting(FILTER_USER_FOLDER_SETTING_NAME, filterUserFolders ? 'true' : undefined)
$: storeLocalSetting(INCLUDE_WITHOUT_MAIN_SETTING_NAME, includeWithoutMain ? 'true' : undefined)
const openSearchWithPrefilledText: (t?: string) => void = getContext("openSearchWithPrefilledText")
const openSearchWithPrefilledText: (t?: string) => void = getContext(
'openSearchWithPrefilledText'
)
let viewCodeDrawer: Drawer
let viewCodeTitle: string | undefined
@@ -396,7 +398,7 @@
</button>
</div>
<Button
on:click={() => openSearchWithPrefilledText("#")}
on:click={() => openSearchWithPrefilledText('#')}
variant="border"
size="sm"
spacingSize="lg"
@@ -421,11 +423,8 @@
{/if}
{#if !loading}
<div class="flex w-full flex-row-reverse gap-2 mt-4 mb-1 items-center h-6">
<Popup
floatingConfig={{ placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ placement: 'bottom-end' }}>
<svelte:fragment slot="trigger">
<Button
startIcon={{
icon: SlidersHorizontal
@@ -438,20 +437,22 @@
spacingSize="xs2"
/>
</svelte:fragment>
<div>
<span class="text-sm font-semibold">Filters</span>
<div class="flex flex-col gap-2 mt-2">
<Toggle size="xs" bind:checked={archived} options={{ right: 'Only archived' }} />
{#if $userStore && !$userStore.operator}
<Toggle
size="xs"
bind:checked={includeWithoutMain}
options={{ right: 'Include without main function' }}
/>
{/if}
<svelte:fragment slot="content">
<div class="p-4">
<span class="text-sm font-semibold">Filters</span>
<div class="flex flex-col gap-2 mt-2">
<Toggle size="xs" bind:checked={archived} options={{ right: 'Only archived' }} />
{#if $userStore && !$userStore.operator}
<Toggle
size="xs"
bind:checked={includeWithoutMain}
options={{ right: 'Include without main function' }}
/>
{/if}
</div>
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
{#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}
@@ -1,13 +1,19 @@
<script lang="ts">
import { melt } from '@melt-ui/svelte'
import { createEventDispatcher } from 'svelte'
import type { MenubarMenuElements } from '@melt-ui/svelte'
import { goto } from '$app/navigation'
export let href: string | undefined = undefined
export let disabled: boolean = false
export let target: string | undefined = undefined
export let item: MenubarMenuElements['item']
const dispatch = createEventDispatcher()
function handleClick(e: MouseEvent) {
if (href && !target) {
e.preventDefault()
goto(href)
}
}
</script>
{#if href}
@@ -19,6 +25,7 @@
aria-disabled={disabled}
tabindex={disabled ? -1 : undefined}
{target}
on:click={handleClick}
on:m-focusin
on:m-focusout
>
@@ -26,7 +33,7 @@
</a>
{:else}
<button
on:click={(e) => dispatch('click', e)}
on:click
use:melt={$item}
{disabled}
class={$$props.class}
@@ -1,48 +1,113 @@
<script context="module" lang="ts">
import { writable } from 'svelte/store'
const activePopover = writable<{ id: string | null; close: (() => void) | null }>({
id: null,
close: null
})
</script>
<script lang="ts">
import { createPopover, createSync, melt } from '@melt-ui/svelte'
import { fade } from 'svelte/transition'
import { X } from 'lucide-svelte'
import type { Placement } from '@floating-ui/core'
import { pointerDownOutside } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
export let open = false
export let closeButton: boolean = true
export let closeButton: boolean = false
export let displayArrow: boolean = false
export let placement: any = 'bottom'
export let placement: Placement = 'bottom'
export let disablePopup: boolean = false
export let openOnHover: boolean = false
export let floatingConfig: any | undefined = undefined
export let usePointerDownOutside: boolean = false
export let closeOnOutsideClick: boolean = true
export let contentClasses: string = ''
export let portal: string | HTMLElement | null = 'body'
export let closeOnOtherPopoverOpen: boolean = false
const {
elements: { trigger, content, arrow, close },
states
elements: { trigger, content, arrow, close: closeElement },
states,
options: { closeOnOutsideClick: closeOnOutsideClickOption },
ids: { content: popoverId }
} = createPopover({
forceVisible: true,
positioning: {
positioning: floatingConfig ?? {
placement
},
portal,
onOpenChange: ({ next }) => {
if (closeOnOtherPopoverOpen) {
if (next) {
// Close previous popover if exists
if ($activePopover.close && $activePopover.id !== $popoverId) {
$activePopover.close()
}
// Set this popover as active
activePopover.set({ id: $popoverId, close })
} else if ($activePopover.id === $popoverId) {
activePopover.set({ id: null, close: null })
}
}
return next
}
})
let isOpen = false
const sync = createSync(states)
$: sync.open(open, (v) => (open = v))
$: sync.open(isOpen, (v) => (isOpen = v))
// Allow for dynamic closeOnOutsideClick
$: $closeOnOutsideClickOption = closeOnOutsideClick
export function close() {
isOpen = false
}
export function open() {
isOpen = true
}
async function getMenuElements(): Promise<HTMLElement[]> {
return Array.from(document.querySelectorAll('[data-popover]')) as HTMLElement[]
}
</script>
<button
class="w-full h-full"
type="button"
class={$$props.class}
use:melt={$trigger}
aria-label="Popup button"
on:mouseenter={() => (openOnHover ? (open = true) : null)}
on:mouseleave={() => (openOnHover ? (open = false) : null)}
on:mouseenter={() => (openOnHover ? open() : null)}
on:mouseleave={() => (openOnHover ? close() : null)}
use:pointerDownOutside={{
capture: true,
stopPropagation: false,
exclude: getMenuElements
}}
on:pointerdown_outside={() => {
if (usePointerDownOutside) {
close()
}
}}
data-popover
>
<slot name="trigger" />
<slot name="trigger" {isOpen} />
</button>
{#if open}
<div use:melt={$content} transition:fade={{ duration: 100 }} class="content z-[9999]">
{#if isOpen && !disablePopup}
<div
use:melt={$content}
transition:fade={{ duration: 0 }}
class={twMerge(' w-fit border rounded-md bg-surface shadow-lg', contentClasses, `z-[5001]`)}
data-popover
>
{#if displayArrow}
<div use:melt={$arrow} />
{/if}
<slot name="content" />
<slot name="content" {open} {close} />
{#if closeButton}
<button class="close" use:melt={$close}>
<button class="close" use:melt={$closeElement}>
<X class="size-3" />
</button>
{/if}
@@ -56,8 +121,4 @@
@apply focus-visible:ring focus-visible:ring-gray-400 focus-visible:ring-offset-2;
@apply bg-surface p-0 text-sm font-medium;
}
.content {
@apply w-fit rounded-[4px] bg-surface p-0 overflow-hidden shadow-md;
}
</style>
@@ -0,0 +1,75 @@
<script lang="ts">
import Markdown from 'svelte-exmarkdown'
import type { Placement } from '@floating-ui/core'
import { ExternalLink, InfoIcon } from 'lucide-svelte'
import { gfmPlugin } from 'svelte-exmarkdown/gfm'
import { zIndexes } from '$lib/zIndexes'
import { createTooltip, melt } from '@melt-ui/svelte'
import { fade } from 'svelte/transition'
export let light = false
export let placement: Placement | undefined = 'bottom'
export let documentationLink: string | undefined = undefined
export let small = false
export let markdownTooltip: string | undefined = undefined
export let disablePopup: boolean = false
export let openDelay: number = 300
export let closeDelay: number = 0
export let portal: string | undefined | null = 'body'
const plugins = [gfmPlugin()]
const {
elements: { trigger, content },
states: { open }
} = createTooltip({
positioning: {
placement
},
openDelay,
closeDelay,
group: true,
portal
})
</script>
<span class={$$props.class} use:melt={$trigger}>
<slot />
</span>
{#if !$$slots.default}
<div
class="inline-flex w-3 mx-0.5 h-3 {light
? 'text-tertiary-inverse'
: 'text-tertiary'} {$$props.class} "
use:melt={$trigger}
>
<InfoIcon size={small ? 12 : 14} />
</div>
{/if}
{#if $open && !disablePopup}
<div
use:melt={$content}
transition:fade={{ duration: 100 }}
class="shadow max-w-sm break-words py-2 px-3 rounded-md text-sm font-normal !text-gray-300 bg-gray-800 whitespace-normal text-left"
style="z-index: {zIndexes.tooltip}"
>
{#if markdownTooltip}
<div class="prose-sm">
<Markdown md={markdownTooltip} {plugins} />
</div>
{:else}
<slot name="text" />
{/if}
{#if documentationLink}
<a href={documentationLink} target="_blank" class="text-blue-300 text-xs">
<div class="flex flex-row gap-2 mt-4">
See documentation
<ExternalLink size="16" />
</div>
</a>
{/if}
</div>
{/if}
@@ -1,18 +1,20 @@
<script lang="ts">
// UNUSED FOR NOW
import { Filter } from 'lucide-svelte'
import { Popup, Button } from '../common'
import { Button } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
</script>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Filter }}>
Filters
</Button>
</svelte:fragment>
<div class="flex flex-col w-80">
<slot name="filters" />
</div>
</Popup>
<svelte:fragment slot="content">
<div class="flex flex-col w-80 p-4">
<slot name="filters" />
</div>
</svelte:fragment>
</Popover>
+302 -303
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Button, Popup } from '../common'
import { Button } from '../common'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import Tooltip from '../Tooltip.svelte'
@@ -17,10 +17,10 @@
import Toggle from '../Toggle.svelte'
import Label from '../Label.svelte'
import Section from '../Section.svelte'
import CloseButton from '../common/CloseButton.svelte'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import ToggleButtonMore from '../common/toggleButton-v2/ToggleButtonMore.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
// Filters
export let path: string | null = null
@@ -441,6 +441,7 @@
<div class="relative">
<span class="text-xs absolute -top-4">Status</span>
<ToggleButtonGroup
allowEmpty
bind:selected={success}
on:selected={() => dispatch('successChange', success)}
>
@@ -487,327 +488,325 @@
</div>
{/if}
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-6 bg-surface"
let:close
contentClasses="p-4"
closeButton
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Filter }}>
More filters
</Button>
</svelte:fragment>
<Section label="Filters">
<svelte:fragment slot="action">
<CloseButton on:close={() => close(null)} />
</svelte:fragment>
<div class="w-102 flex flex-col gap-4">
{#if mobile}
<Label label="Filter by">
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!autoSet) {
path = null
user = null
folder = null
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
} else {
autoSet = false
}
}}
>
<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" />
</ToggleButtonGroup>
</Label>
{#if filterBy == 'user'}
{#key user}
<Label label="User">
<div class="relative w-full">
{#if user}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy == 'folder'}
{#key folder}
<Label label="Folder">
<div class="relative w-full">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy === 'path'}
{#key path}
<Label label="Path">
<div class="relative w-full">
{#if path}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy === 'tag'}
{#key tag}
<Label label="Tag">
<div class="relative w-full">
{#if tag}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
tag = null
}}
>
<X size={14} />
</button>
{/if}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedTag}
on:keydown={(e) => {
if (tagTimeout) {
clearTimeout(tagTimeout)
}
tagTimeout = setTimeout(() => {
tag = displayedTag
console.log(tag)
}, 1000)
}}
/>
</div></Label
<svelte:fragment slot="content">
<Section label="Filters">
<div class="w-102 flex flex-col gap-4">
{#if mobile || true}
<Label label="Filter by">
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!autoSet) {
path = null
user = null
folder = null
label = null
concurrencyKey = null
tag = null
schedulePath = undefined
} else {
autoSet = false
}
}}
>
{/key}
{:else if filterBy === 'label'}
{#key label}
<Label label="Label">
<div class="relative w-full">
{#if label}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
label = null
<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" />
</ToggleButtonGroup>
</Label>
{#if filterBy == 'user'}
{#key user}
<Label label="User">
<div class="relative w-full">
{#if user}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy == 'folder'}
{#key folder}
<Label label="Folder">
<div class="relative w-full">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy === 'path'}
{#key path}
<Label label="Path">
<div class="relative w-full">
{#if path}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{:else if filterBy === 'tag'}
{#key tag}
<Label label="Tag">
<div class="relative w-full">
{#if tag}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
tag = null
}}
>
<X size={14} />
</button>
{/if}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedTag}
on:keydown={(e) => {
if (tagTimeout) {
clearTimeout(tagTimeout)
}
tagTimeout = setTimeout(() => {
tag = displayedTag
console.log(tag)
}, 1000)
}}
>
<X size={14} />
</button>
{/if}
/>
</div></Label
>
{/key}
{:else if filterBy === 'label'}
{#key label}
<Label label="Label">
<div class="relative w-full">
{#if label}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
label = null
}}
>
<X size={14} />
</button>
{/if}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedLabel}
on:keydown={(e) => {
if (labelTimeout) {
clearTimeout(labelTimeout)
}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedLabel}
on:keydown={(e) => {
if (labelTimeout) {
clearTimeout(labelTimeout)
}
labelTimeout = setTimeout(() => {
label = displayedLabel
}, 1000)
}}
/>
</div></Label
>
{/key}
{:else if filterBy === 'concurrencyKey'}
{#key concurrencyKey}
<Label label="Concurrency key">
<div class="relative w-full">
{#if concurrencyKey}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
concurrencyKey = null
// dispatch('reset')
labelTimeout = setTimeout(() => {
label = displayedLabel
}, 1000)
}}
>
<X size={14} />
</button>
{/if}
/>
</div></Label
>
{/key}
{:else if filterBy === 'concurrencyKey'}
{#key concurrencyKey}
<Label label="Concurrency key">
<div class="relative w-full">
{#if concurrencyKey}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
concurrencyKey = null
// dispatch('reset')
}}
>
<X size={14} />
</button>
{/if}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedConcurrencyKey}
on:keydown={(e) => {
if (concurrencyKeyTimeout) {
clearTimeout(concurrencyKeyTimeout)
}
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus
type="text"
class="!h-[32px] py-1 !text-xs !w-80"
bind:value={displayedConcurrencyKey}
on:keydown={(e) => {
if (concurrencyKeyTimeout) {
clearTimeout(concurrencyKeyTimeout)
}
concurrencyKeyTimeout = setTimeout(() => {
concurrencyKey = displayedConcurrencyKey
}, 1000)
}}
/>
</div>
</Label>
{/key}
concurrencyKeyTimeout = setTimeout(() => {
concurrencyKey = displayedConcurrencyKey
}, 1000)
}}
/>
</div>
</Label>
{/key}
{/if}
<Label label="Kind">
<ToggleButtonGroup bind:selected={jobKindsCat}>
<ToggleButton value="all" label="All" />
<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"
/>
<ToggleButton
value="previews"
label="Previews"
showTooltipIcon
tooltip="Previews are jobs that have been started in the editor as 'Tests'"
/>
<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."
/>
<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"
/>
</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>
</Label>
{/if}
<Label label="Kind">
<ToggleButtonGroup bind:selected={jobKindsCat}>
<ToggleButton value="all" label="All" />
<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"
/>
<ToggleButton
value="previews"
label="Previews"
showTooltipIcon
tooltip="Previews are jobs that have been started in the editor as 'Tests'"
/>
<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."
/>
<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"
/>
</ToggleButtonGroup>
<Label label="Show skipped flows">
<div class="flex flex-row gap-1 items-center">
<Toggle size="xs" bind:checked={isSkipped} />
<Tooltip>Skipped flows are flows that did an early break</Tooltip>
</div>
</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>
<span class="text-xs leading-6">
{`Filter by a json being a subset of the args/result. Try '\{"foo": "bar"\}'`}
</span>
<Label label="Filter by args">
<JsonEditor bind:error={argError} bind:code={copyArgFilter} />
</Label>
<Label label="Filter by result">
<JsonEditor bind:error={resultError} bind:code={copyResultFilter} />
</Label>
{/if}
<Label label="Show skipped flows">
<div class="flex flex-row gap-1 items-center">
<Toggle size="xs" bind:checked={isSkipped} />
<Tooltip>Skipped flows are flows that did an early break</Tooltip>
<div class="flex flex-row gap-2 justify-between">
<Button
size="xs"
color="light"
on:click={() => {
argFilter = ''
resultFilter = ''
}}
>
Clear
</Button>
<Button
size="xs"
color="dark"
on:click={() => {
argFilter = copyArgFilter
resultFilter = copyResultFilter
}}
>
Set args/result filter
</Button>
</div>
</Label>
<span class="text-xs leading-6">
{`Filter by a json being a subset of the args/result. Try '\{"foo": "bar"\}'`}
</span>
<Label label="Filter by args">
<JsonEditor bind:error={argError} bind:code={copyArgFilter} />
</Label>
<Label label="Filter by result">
<JsonEditor bind:error={resultError} bind:code={copyResultFilter} />
</Label>
<div class="flex flex-row gap-2 justify-between">
<Button
size="xs"
color="light"
on:click={() => {
argFilter = ''
resultFilter = ''
}}
>
Clear
</Button>
<Button
size="xs"
color="dark"
on:click={() => {
argFilter = copyArgFilter
resultFilter = copyResultFilter
}}
>
Set args/result filter
</Button>
</div>
</div>
</Section>
</Popup>
</Section>
</svelte:fragment>
</Popover>
</div>
@@ -13,15 +13,13 @@
dispatch('add', { name })
name = ''
}
let open: boolean = false
</script>
<Popover closeButton={false} bind:open>
<Popover closeButton={false} class="w-full">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
<svelte:fragment slot="content" let:close>
<div class="flex flex-row gap-2 p-2 rounded-md">
<input
bind:value={name}
@@ -29,7 +27,7 @@
on:keydown={(event) => {
if (event.key === 'Enter') {
addField()
open = false
close()
}
}}
/>
@@ -40,7 +38,7 @@
id="flow-editor-add-property"
on:click={() => {
addField()
open = false
close()
}}
disabled={!name}
shortCut={{ Icon: CornerDownLeft, withoutModifier: true }}
@@ -16,7 +16,7 @@
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import Button from '../common/button/Button.svelte'
import { Pen, Plus, Trash2 } from 'lucide-svelte'
import Popup from '../common/popup/Popup.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { deepEqual } from 'fast-equals'
export let format: string | undefined = undefined
@@ -180,30 +180,26 @@
<div class="flex flex-col gap-2">
{#if type === 'object' && schema.oneOf && schema.oneOf.length >= 2}
<div class="flex flex-row gap-2 items-center justify-start">
<ToggleButtonGroup bind:selected={oneOfSelected} class="w-auto">
<div class="flex flex-row gap-1 items-center justify-start">
<ToggleButtonGroup bind:selected={oneOfSelected} class="w-auto" tabListClass="flex-wrap">
{#each schema.oneOf as obj}
<ToggleButton value={obj.title} label={obj.title} />
{/each}
</ToggleButtonGroup>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Popover placement="bottom-end" closeButton>
<svelte:fragment slot="trigger">
<Button size="xs2" color="light" nonCaptureEvent startIcon={{ icon: Plus }} />
</svelte:fragment>
<Label label="Label">
<div class="flex flex-col gap-2">
<svelte:fragment slot="content" let:close>
<Label label="Label" class="p-2 flex flex-col gap-2">
<input
type="text"
class="w-full !bg-surface"
on:keydown={(event) => {
if (event.key === 'Enter') {
createVariant(variantName)
close(null)
close()
}
}}
bind:value={variantName}
@@ -214,25 +210,25 @@
size="xs"
on:click={() => {
createVariant(variantName)
close(null)
close()
}}
disabled={!variantName}
disabled={variantName.length === 0}
>
Add
</Button>
</div>
</Label>
</Popup>
</Label>
</svelte:fragment>
</Popover>
</div>
<div class="flex flex-row gap-2 items-center">
<span class="font-semibold text-sm">{oneOfSelected}</span>
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
closeButton
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
size="xs2"
color="light"
@@ -246,8 +242,8 @@
}}
/>
</svelte:fragment>
<Label label="Label">
<div class="flex flex-col gap-2">
<svelte:fragment slot="content" let:close>
<Label label="Label" class="p-2 flex flex-col gap-2">
<input
type="text"
class="w-full !bg-surface"
@@ -255,7 +251,7 @@
if (event.key === 'Enter') {
if (oneOfSelected) {
renameVariant(variantName, oneOfSelected)
close(null)
close()
}
}
}}
@@ -268,16 +264,16 @@
on:click={() => {
if (oneOfSelected) {
renameVariant(variantName, oneOfSelected)
close(null)
close()
}
}}
disabled={!variantName}
disabled={variantName.length === 0}
>
Rename
</Button>
</div>
</Label>
</Popup>
</Label>
</svelte:fragment>
</Popover>
<Button
size="xs2"
color="red"
@@ -1,6 +1,7 @@
<script lang="ts">
import AddUser from '$lib/components/AddUser.svelte'
import { Badge, Button, Popup, Skeleton } from '$lib/components/common'
import { Badge, Button, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import WorkspaceOperatorSettings from '$lib/components/settings/WorkspaceOperatorSettings.svelte'
@@ -153,11 +154,11 @@
<input placeholder="Filter members" bind:value={userFilter} class="input !pl-8" />
<Search class="absolute left-2" size={14} />
<Popup
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
usePointerDownOutside
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button
color={auto_invite_domain != undefined ? 'green' : 'red'}
variant="border"
@@ -169,113 +170,117 @@
: 'OFF'}
</Button>
</svelte:fragment>
<div class="flex flex-col items-start">
<span class="text-sm leading-6 font-semibold">
{isCloudHosted()
? `Auto-add anyone from ${
auto_invite_domain != undefined ? auto_invite_domain : domain
}`
: `Auto-add anyone joining the instance`}
</span>
{#if showInvites}
<span class="text-xs mb-1 leading-6 pt-2"
>Mode <Tooltip>Whether to invite or add users directly to the workspace.</Tooltip>
<svelte:fragment slot="content">
<div class="flex flex-col items-start p-4">
<span class="text-sm leading-6 font-semibold">
{isCloudHosted()
? `Auto-add anyone from ${
auto_invite_domain != undefined ? auto_invite_domain : domain
}`
: `Auto-add anyone joining the instance`}
</span>
{#if showInvites}
<span class="text-xs mb-1 leading-6 pt-2"
>Mode <Tooltip>Whether to invite or add users directly to the workspace.</Tooltip>
</span>
<ToggleButtonGroup
selected={autoAdd ? 'add' : 'invite'}
on:selected={async (e) => {
if (auto_invite_domain != undefined) {
await removeAllInvitesFromDomain()
await WorkspaceService.editAutoInvite({
workspace: $workspaceStore ?? '',
requestBody: {
operator: operatorOnly ?? false,
invite_all: !isCloudHosted(),
auto_add: e.detail === 'add'
}
})
loadSettings()
listInvites()
listUsers()
autoAdd = e.detail === 'add'
} else {
autoAdd = e.detail === 'add'
}
}}
>
<ToggleButton value="invite" size="xs" label="Auto-invite" />
<ToggleButton value="add" size="xs" label="Auto-add" />
</ToggleButtonGroup>
{/if}
<span class="text-xs mb-1 leading-6 pt-2"
>Role <Tooltip>Role of the auto-added users</Tooltip></span
>
<ToggleButtonGroup
selected={autoAdd ? 'add' : 'invite'}
selected={operatorOnly ? 'operator' : 'developer'}
on:selected={async (e) => {
if (auto_invite_domain != undefined) {
await removeAllInvitesFromDomain()
await WorkspaceService.editAutoInvite({
workspace: $workspaceStore ?? '',
requestBody: {
operator: operatorOnly ?? false,
operator: e.detail === 'operator',
invite_all: !isCloudHosted(),
auto_add: e.detail === 'add'
auto_add: showInvites ? autoAdd ?? false : true
}
})
operatorOnly = e.detail === 'operator'
loadSettings()
listInvites()
listUsers()
} else {
autoAdd = e.detail === 'add'
operatorOnly = e.detail === 'operator'
}
}}
>
<ToggleButton value="invite" size="xs" label="Auto-invite" />
<ToggleButton value="add" size="xs" label="Auto-add" />
<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."
/>
<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)."
/>
</ToggleButtonGroup>
{/if}
<span class="text-xs mb-1 leading-6 pt-2"
>Role <Tooltip>Role of the auto-added users</Tooltip></span
>
<ToggleButtonGroup
selected={operatorOnly ? 'operator' : 'developer'}
on:selected={async (e) => {
if (auto_invite_domain != undefined) {
await removeAllInvitesFromDomain()
await WorkspaceService.editAutoInvite({
workspace: $workspaceStore ?? '',
requestBody: {
operator: e.detail === 'operator',
invite_all: !isCloudHosted(),
auto_add: showInvites ? autoAdd ?? false : true
}
})
loadSettings()
listInvites()
listUsers()
} else {
operatorOnly = e.detail === 'operator'
}
}}
>
<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."
/>
<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)."
/>
</ToggleButtonGroup>
<div class="pt-2">
<Toggle
size="xs"
checked={auto_invite_domain != undefined}
on:change={async (e) => {
await removeAllInvitesFromDomain()
await WorkspaceService.editAutoInvite({
workspace: $workspaceStore ?? '',
requestBody: e.detail
? {
operator: operatorOnly ?? false,
invite_all: !isCloudHosted(),
auto_add: showInvites ? autoAdd ?? false : true
}
: { operator: undefined, auto_add: undefined }
})
loadSettings()
listInvites()
listUsers()
}}
disabled={isCloudHosted() && !allowedAutoDomain}
options={{
right: 'Enabled'
}}
/>
<div class="pt-2">
<Toggle
size="xs"
checked={auto_invite_domain != undefined}
on:change={async (e) => {
await removeAllInvitesFromDomain()
await WorkspaceService.editAutoInvite({
workspace: $workspaceStore ?? '',
requestBody: e.detail
? {
operator: operatorOnly ?? false,
invite_all: !isCloudHosted(),
auto_add: showInvites ? autoAdd ?? false : true
}
: { operator: undefined, auto_add: undefined }
})
loadSettings()
listInvites()
listUsers()
}}
disabled={isCloudHosted() && !allowedAutoDomain}
options={{
right: 'Enabled'
}}
/>
</div>
{#if isCloudHosted() && !allowedAutoDomain}
<div class="text-red-400 text-xs">{domain} domain not allowed for auto-add</div>
{/if}
</div>
{#if isCloudHosted() && !allowedAutoDomain}
<div class="text-red-400 text-xs">{domain} domain not allowed for auto-add</div>
{/if}
</div>
</Popup>
</svelte:fragment>
</Popover>
<AddUser
on:new={() => {
listUsers()
@@ -11,7 +11,8 @@
superadmin
} from '$lib/stores'
import Modal2 from '../common/modal/Modal2.svelte'
import { Button, Popup } from '$lib/components/common'
import { Button } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import List from '$lib/components/common/layout/List.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { BellOff, Bell, ExternalLink, Settings } from 'lucide-svelte'
@@ -171,12 +172,12 @@
<svelte:fragment slot="header-right">
<List horizontal>
{#if $superadmin || $userStore?.is_admin}
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
target="#mute-settings-button"
preventPopupClosingOnClickInside={true}
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-end' }}
portal="#mute-settings-button"
contentClasses="p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<div id="mute-settings-button">
<Button variant="border" color="light" nonCaptureEvent>
{#if muteSettings.global || muteSettings.workspace}
@@ -187,76 +188,81 @@
</Button>
</div>
</svelte:fragment>
<List justify="start">
<div class="w-full">
{#if $superadmin}
<svelte:fragment slot="content">
<List justify="start">
<div class="w-full">
{#if $superadmin}
<Toggle
on:change={saveGlobalMuteSetting}
bind:checked={muteSettings.global}
options={{
right: 'Automatically acknowledge critical alerts instance wide'
}}
size="xs"
stopPropagation={true}
/>
{/if}
</div>
<div class="w-full">
<Toggle
on:change={saveGlobalMuteSetting}
bind:checked={muteSettings.global}
on:change={saveWorkSpaceMuteSetting}
bind:checked={muteSettings.workspace}
options={{
right: 'Automatically acknowledge critical alerts instance wide'
right: 'Automatically acknowledge critical alerts for current workspace'
}}
size="xs"
stopPropagation={true}
/>
{/if}
</div>
<div class="w-full">
<Toggle
on:change={saveWorkSpaceMuteSetting}
bind:checked={muteSettings.workspace}
options={{
right: 'Automatically acknowledge critical alerts for current workspace'
}}
size="xs"
stopPropagation={true}
/>
</div>
</List>
</Popup>
</div>
</List>
</svelte:fragment>
</Popover>
{/if}
{#if $superadmin}
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
target="#settings-button"
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-end' }}
portal="#settings-button"
contentClasses="p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<div id="settings-button">
<Button variant="border" color="light" nonCaptureEvent>
<Settings size="16" />
</Button>
</div>
</svelte:fragment>
<List justify="start" gap="none">
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/?workspace=admins#superadmin-settings"
target="_blank"
>
<div class="w-full">
<List horizontal justify="between" gap="sm">
<div>Instance Critical Alert Settings</div>
<ExternalLink size="16" />
</List>
</div>
</Button>
</div>
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/workspace_settings?tab=error_handler"
target="_blank"
>
Workspace Critical Alert Settings <ExternalLink size="16" />
</Button>
</div>
</List>
</Popup>
<svelte:fragment slot="content">
<List justify="start" gap="none">
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/?workspace=admins#superadmin-settings"
target="_blank"
>
<div class="w-full">
<List horizontal justify="between" gap="sm">
<div>Instance Critical Alert Settings</div>
<ExternalLink size="16" />
</List>
</div>
</Button>
</div>
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/workspace_settings?tab=error_handler"
target="_blank"
>
Workspace Critical Alert Settings <ExternalLink size="16" />
</Button>
</div>
</List>
</svelte:fragment>
</Popover>
{:else}
<Button
size="xs"
@@ -26,7 +26,7 @@
}
</script>
<Popover closeButton={false} bind:open={isOpen}>
<Popover closeButton={false} bind:isOpen>
<svelte:fragment slot="trigger">
{#if small}
<Button
@@ -16,7 +16,7 @@
<Tutorial
bind:this={tutorial}
index={0}
index={4}
name="error-handler"
tainted={false}
on:error
@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Label from '../Label.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import Button from '../common/button/Button.svelte'
@@ -33,36 +33,38 @@
}
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 gap-4 p-4 max-h-[50vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<OneOfInputSpecsEditor
key={'Data'}
bind:oneOf={value.value}
id={$selectedComponent?.[0] ?? ''}
shouldCapitalize={true}
resourceOnly={false}
inputSpecsConfiguration={value.value?.['configuration']}
labels={value.value?.['labels']}
tooltip={value.value?.['tooltip']}
disabledOptions={isEE ? [] : ['range-bar', 'range-area']}
/>
<OneOfInputSpecsEditor
key={'Data'}
bind:oneOf={value.value}
id={$selectedComponent?.[0] ?? ''}
shouldCapitalize={true}
resourceOnly={false}
inputSpecsConfiguration={value.value?.['configuration']}
labels={value.value?.['labels']}
tooltip={value.value?.['tooltip']}
disabledOptions={isEE ? [] : ['range-bar', 'range-area']}
/>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</Popup>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
</Popover>
@@ -1,10 +1,11 @@
<script lang="ts">
import { Alert, Popup } from '../common'
import { Alert } from '../common'
import Toggle from '$lib/components/Toggle.svelte'
import SimpleEditor from '../SimpleEditor.svelte'
import Label from '../Label.svelte'
import Tooltip from '../Tooltip.svelte'
import Button from '../common/button/Button.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
type Column = {
@@ -88,162 +89,165 @@
}
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<span class="text-sm mb-2 leading-6 font-semibold">
Column definitions
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-definitions/"
>
Column definitions are used to define columns in ag-Grid.
</Tooltip>
</span>
<Label label="Header name">
<input type="text" placeholder="Header name" bind:value={value.headerName} />
</Label>
<Label label="Editable value">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Editable' }}
bind:checked={value.editable}
size="xs"
/>
</Label>
<Label label="Min width (px)">
<input type="number" placeholder="width" bind:value={value.minWidth} />
</Label>
<Label label="Flex">
<svelte:fragment slot="header">
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<span class="text-sm mb-2 leading-6 font-semibold">
Column definitions
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-sizing/#column-flex"
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-definitions/"
>
It's often required that one or more columns fill the entire available space in the
grid. For this scenario, it is possible to use the flex config. Some columns could be
set with a regular width config, while other columns would have a flex config. Flex
sizing works by dividing the remaining space in the grid among all flex columns in
proportion to their flex value. For example, suppose the grid has a total width of 450px
and it has three columns: the first with width: 150; the second with flex: 1; and third
with flex: 2. The first column will be 150px wide, leaving 300px remaining. The column
with flex: 2 has twice the size with flex: 1. So final sizes will be: 150px, 100px,
200px.
Column definitions are used to define columns in ag-Grid.
</Tooltip>
</svelte:fragment>
</span>
<input type="range" step="1" bind:value={value.flex} min={1} max={12} />
<div class="text-xs">{value.flex}</div>
</Label>
<Label label="Header name">
<input type="text" placeholder="Header name" bind:value={value.headerName} />
</Label>
<Label label="Hide">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide' }}
bind:checked={value.hide}
size="xs"
/>
</Label>
<Label label="Value formatter">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/value-formatters/"
>
Value formatters allow you to format values for display. This is useful when data is one
type (e.g. numeric) but needs to be converted for human reading (e.g. putting in
currency symbols and number formatting).
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
<Button
size="xs"
color="light"
variant="border"
on:click={() => {
// @ts-ignore
value.valueFormatter = null
renderCount++
<Label label="Editable value">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
>
Clear
</Button>
</svelte:fragment>
</Label>
<div>
{#key renderCount}
<div class="flex flex-col gap-4">
<div class="relative">
{#if !presets.find((preset) => preset.value === value?.valueFormatter)}
<div class="z-50 absolute bg-opacity-50 bg-surface top-0 left-0 bottom-0 right-0" />
{/if}
<div class="text-xs font-semibold">Presets</div>
<select
bind:value={value.valueFormatter}
on:change={() => {
renderCount++
}}
placeholder="Code"
>
{#each presets as preset}
<option value={preset.value}>{preset.label}</option>
{/each}
</select>
options={{ right: 'Editable' }}
bind:checked={value.editable}
size="xs"
/>
</Label>
<Label label="Min width (px)">
<input type="number" placeholder="width" bind:value={value.minWidth} />
</Label>
<Label label="Flex">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-sizing/#column-flex"
>
It's often required that one or more columns fill the entire available space in the
grid. For this scenario, it is possible to use the flex config. Some columns could be
set with a regular width config, while other columns would have a flex config. Flex
sizing works by dividing the remaining space in the grid among all flex columns in
proportion to their flex value. For example, suppose the grid has a total width of
450px and it has three columns: the first with width: 150; the second with flex: 1;
and third with flex: 2. The first column will be 150px wide, leaving 300px remaining.
The column with flex: 2 has twice the size with flex: 1. So final sizes will be:
150px, 100px, 200px.
</Tooltip>
</svelte:fragment>
<input type="range" step="1" bind:value={value.flex} min={1} max={12} />
<div class="text-xs">{value.flex}</div>
</Label>
<Label label="Hide">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide' }}
bind:checked={value.hide}
size="xs"
/>
</Label>
<Label label="Value formatter">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/value-formatters/"
>
Value formatters allow you to format values for display. This is useful when data is
one type (e.g. numeric) but needs to be converted for human reading (e.g. putting in
currency symbols and number formatting).
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
<Button
size="xs"
color="light"
variant="border"
on:click={() => {
// @ts-ignore
value.valueFormatter = null
renderCount++
}}
>
Clear
</Button>
</svelte:fragment>
</Label>
<div>
{#key renderCount}
<div class="flex flex-col gap-4">
<div class="relative">
{#if !presets.find((preset) => preset.value === value?.valueFormatter)}
<div
class="z-50 absolute bg-opacity-50 bg-surface top-0 left-0 bottom-0 right-0"
/>
{/if}
<div class="text-xs font-semibold">Presets</div>
<select
bind:value={value.valueFormatter}
on:change={() => {
renderCount++
}}
placeholder="Code"
>
{#each presets as preset}
<option value={preset.value}>{preset.label}</option>
{/each}
</select>
</div>
<SimpleEditor
extraLib={'declare const value: any'}
autoHeight
lang="javascript"
bind:code={value.valueFormatter}
/>
<div class="text-xs text-secondary -mt-4">Use `value` in the formatter</div>
</div>
{/key}
</div>
<SimpleEditor
extraLib={'declare const value: any'}
autoHeight
lang="javascript"
bind:code={value.valueFormatter}
/>
<div class="text-xs text-secondary -mt-4">Use `value` in the formatter</div>
</div>
{/key}
</div>
<Label label="Sort">
<select bind:value={value.sort}>
<option value={null}>None</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</Label>
<Label label="Sort">
<select bind:value={value.sort}>
<option value={null}>None</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</Label>
<Label label="Filter">
<svelte:fragment slot="header">
<Tooltip documentationLink="https://www.ag-grid.com/javascript-data-grid/filtering/">
Filtering allows you to limit the rows displayed in your grid to those that match
criteria you specify.
</Tooltip>
</svelte:fragment>
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Enable filter' }}
bind:checked={value.filter}
size="xs"
/>
</Label>
<Label label="Filter">
<svelte:fragment slot="header">
<Tooltip documentationLink="https://www.ag-grid.com/javascript-data-grid/filtering/">
Filtering allows you to limit the rows displayed in your grid to those that match
criteria you specify.
</Tooltip>
</svelte:fragment>
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Enable filter' }}
bind:checked={value.filter}
size="xs"
/>
</Label>
<!--
<!--
EE only
<Label label="Aggregation function">
@@ -275,27 +279,28 @@
</Label>
-->
<Label label="Type">
<select bind:value={value.cellRendererType}>
<option value="text">Text</option>
<option value="link">Link</option>
</select>
</Label>
<Label label="Type">
<select bind:value={value.cellRendererType}>
<option value="text">Text</option>
<option value="link">Link</option>
</select>
</Label>
{#if value.cellRendererType === 'link'}
<Alert type="info" title="Label" size="xs">
They are two ways to define a link:
<ul class="list-disc list-inside">
<li>
<strong>String</strong>: The string will be used as the link and the label.
</li>
<li>
<strong>Object</strong>: The object must have a <code>href</code> and a
<code>label</code> property.
</li>
</ul>
</Alert>
{/if}
</div>
{/if}
</Popup>
{#if value.cellRendererType === 'link'}
<Alert type="info" title="Label" size="xs">
They are two ways to define a link:
<ul class="list-disc list-inside">
<li>
<strong>String</strong>: The string will be used as the link and the label.
</li>
<li>
<strong>Object</strong>: The object must have a <code>href</code> and a
<code>label</code> property.
</li>
</ul>
</Alert>
{/if}
</div>
{/if}
</svelte:fragment>
</Popover>
@@ -1,12 +1,11 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import { Popup } from '../common'
import Label from '../Label.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import Button from '../common/button/Button.svelte'
import InputsSpecEditor from '../apps/editor/settingsPanel/InputsSpecEditor.svelte'
import type { AppViewerContext, RichConfiguration } from '../apps/types'
import Popover from '$lib/components/meltComponents/Popover.svelte'
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
type Dataset = {
@@ -23,42 +22,44 @@
}
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<InputsSpecEditor
key={'Data'}
bind:componentInput={value.value}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.value?.['fieldType']}
subFieldType={value.value?.['subFieldType']}
format={value.value?.['format']}
selectOptions={value.value?.['selectOptions']}
tooltip={value.value?.['tooltip']}
fileUpload={value.value?.['fileUpload']}
placeholder={value.value?.['placeholder']}
customTitle={value.value?.['customTitle']}
displayType={false}
/>
<InputsSpecEditor
key={'Data'}
bind:componentInput={value.value}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.value?.['fieldType']}
subFieldType={value.value?.['subFieldType']}
format={value.value?.['format']}
selectOptions={value.value?.['selectOptions']}
tooltip={value.value?.['tooltip']}
fileUpload={value.value?.['fileUpload']}
placeholder={value.value?.['placeholder']}
customTitle={value.value?.['customTitle']}
displayType={false}
/>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</Popup>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
</Popover>
@@ -1,5 +1,6 @@
<script lang="ts">
import { Badge, Popup, type AlertType } from '../common'
import { Badge, type AlertType } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import SimpleEditor from '../SimpleEditor.svelte'
import Label from '../Label.svelte'
@@ -120,19 +121,21 @@
$: warning = computeWarning(value, value)
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-start',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4 max-h-[512px] overflow-y-auto"
contentClasses="max-h-[50vh] overflow-y-auto p-4 flex flex-col gap-4 w-96"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<svelte:fragment slot="content">
{#if value}
<Section label="Column settings">
<svelte:fragment slot="header">
<Badge color="blue">
@@ -388,6 +391,6 @@
</Label>
</div>
</Section>
</div>
{/if}
</Popup>
{/if}
</svelte:fragment>
</Popover>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import type { NavbarItem } from '../apps/editor/component'
import Label from '../Label.svelte'
@@ -16,121 +16,122 @@
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeButton
contentClasses="p-4 max-h-[70vh] overflow-y-auto"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<Section label="Navbar item" class="flex flex-col gap-2 w-80 overflow-y-auto max-h-screen">
<InputsSpecEditor
key={'Label'}
bind:componentInput={value.label}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.label?.['fieldType']}
subFieldType={value.label?.['subFieldType']}
format={value.label?.['format']}
selectOptions={value.label?.['selectOptions']}
fileUpload={value.label?.['fileUpload']}
placeholder={value.label?.['placeholder']}
customTitle={value.label?.['customTitle']}
displayType={false}
/>
<OneOfInputSpecsEditor
key={'Link'}
bind:oneOf={value.path}
id={$selectedComponent?.[0] ?? ''}
shouldCapitalize={true}
resourceOnly={false}
inputSpecsConfiguration={value.path?.['configuration']}
labels={value.path?.['labels']}
tooltip={value.path?.['tooltip']}
/>
<Alert size="xs" title="Link Behavior" collapsible>
<ul class="list-disc">
<li>
If you select an app, there are two cases:
<div class="ml-2">
<ul class="list-disc">
<li>
You selected the current app itself: Clicking on the link will highlight the item,
and set the app in the output. Note that adding query params or an hash lets you
distinguish between different items. Also note that query params can be retrieved
from the context: `ctx.query`.
</li>
<li>
You selected another app: Clicking on the link navigates to the selected app
without reloading the page. In the editor, it will open in a new tab.
</li>
</ul>
</div>
</li>
<li>
If you select an external link, clicking on the link will navigate to the selected link
in a new tab.
</li>
</ul>
</Alert>
<InputsSpecEditor
key={'Disabled'}
bind:componentInput={value.disabled}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.disabled?.['fieldType']}
subFieldType={value.disabled?.['subFieldType']}
format={value.disabled?.['format']}
selectOptions={value.disabled?.['selectOptions']}
fileUpload={value.disabled?.['fileUpload']}
placeholder={value.disabled?.['placeholder']}
customTitle={value.disabled?.['customTitle']}
displayType={false}
/>
<InputsSpecEditor
key={'Hidden'}
bind:componentInput={value.hidden}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.hidden?.['fieldType']}
subFieldType={value.hidden?.['subFieldType']}
format={value.hidden?.['format']}
selectOptions={value.hidden?.['selectOptions']}
fileUpload={value.hidden?.['fileUpload']}
placeholder={value.hidden?.['placeholder']}
customTitle={value.hidden?.['customTitle']}
displayType={false}
/>
<Label label="Icon" class="w-full">
<IconSelectInput
bind:value={value.icon}
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
shouldUsePortal={false}
<svelte:fragment slot="content">
{#if value}
<Section label="Navbar item" class="flex flex-col gap-2 w-80 overflow-y-auto max-h-screen">
<InputsSpecEditor
key={'Label'}
bind:componentInput={value.label}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.label?.['fieldType']}
subFieldType={value.label?.['subFieldType']}
format={value.label?.['format']}
selectOptions={value.label?.['selectOptions']}
fileUpload={value.label?.['fileUpload']}
placeholder={value.label?.['placeholder']}
customTitle={value.label?.['customTitle']}
displayType={false}
/>
</Label>
<Label label="Caption">
<input type="text" bind:value={value.caption} />
</Label>
</Section>
{/if}
</Popup>
<OneOfInputSpecsEditor
key={'Link'}
bind:oneOf={value.path}
id={$selectedComponent?.[0] ?? ''}
shouldCapitalize={true}
resourceOnly={false}
inputSpecsConfiguration={value.path?.['configuration']}
labels={value.path?.['labels']}
tooltip={value.path?.['tooltip']}
/>
<Alert size="xs" title="Link Behavior" collapsible>
<ul class="list-disc">
<li>
If you select an app, there are two cases:
<div class="ml-2">
<ul class="list-disc">
<li>
You selected the current app itself: Clicking on the link will highlight the
item, and set the app in the output. Note that adding query params or an hash
lets you distinguish between different items. Also note that query params can be
retrieved from the context: `ctx.query`.
</li>
<li>
You selected another app: Clicking on the link navigates to the selected app
without reloading the page. In the editor, it will open in a new tab.
</li>
</ul>
</div>
</li>
<li>
If you select an external link, clicking on the link will navigate to the selected
link in a new tab.
</li>
</ul>
</Alert>
<InputsSpecEditor
key={'Disabled'}
bind:componentInput={value.disabled}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.disabled?.['fieldType']}
subFieldType={value.disabled?.['subFieldType']}
format={value.disabled?.['format']}
selectOptions={value.disabled?.['selectOptions']}
fileUpload={value.disabled?.['fileUpload']}
placeholder={value.disabled?.['placeholder']}
customTitle={value.disabled?.['customTitle']}
displayType={false}
/>
<InputsSpecEditor
key={'Hidden'}
bind:componentInput={value.hidden}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.hidden?.['fieldType']}
subFieldType={value.hidden?.['subFieldType']}
format={value.hidden?.['format']}
selectOptions={value.hidden?.['selectOptions']}
fileUpload={value.hidden?.['fileUpload']}
placeholder={value.hidden?.['placeholder']}
customTitle={value.hidden?.['customTitle']}
displayType={false}
/>
<Label label="Icon" class="w-full">
<IconSelectInput
bind:value={value.icon}
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
/>
</Label>
<Label label="Caption">
<input type="text" bind:value={value.caption} />
</Label>
</Section>
{/if}
</svelte:fragment>
</Popover>
@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Label from '../Label.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import ColorInput from '../apps/editor/settingsPanel/inputEditor/ColorInput.svelte'
@@ -11,6 +11,8 @@
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let closeOnOutsideClick = true
type Dataset = {
value: RichConfiguration
name: string
@@ -30,93 +32,101 @@
}
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
{closeOnOutsideClick}
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#if value}
<div class="flex flex-col w-96 p-2 gap-4">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<svelte:fragment slot="content">
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[50vh] overflow-y-auto">
<Label label="Name">
<input type="text" bind:value={value.name} />
</Label>
<Label label="Type">
<select bind:value={value.type}>
<option value="bar">Bar</option>
<option
value="scatter"
on:click={() => {
if (value && value?.extraOptions === undefined) {
value.extraOptions = { mode: 'markers' }
}
}}
>
Scatter
</option>
</select>
</Label>
{#if value.type === 'scatter' && value.extraOptions}
<Label label="Mode">
<select bind:value={value.extraOptions.mode}>
<option value="markers">Markers</option>
<option value="lines">Lines</option>
<option value="lines+markers">Lines + Markers</option>
<Label label="Type">
<select bind:value={value.type}>
<option value="bar">Bar</option>
<option
value="scatter"
on:click={() => {
if (value && value?.extraOptions === undefined) {
value.extraOptions = { mode: 'markers' }
}
}}
>
Scatter
</option>
</select>
</Label>
{/if}
<Label label="Aggregation method">
<svelte:fragment slot="header">
<Tooltip>
A method to aggregate the data. For example, if you have multiple x data points with the
same value, you can choose to sum them up or take the mean. If you don't have multiple x
data points with the same value, this option will have no effect.
</Tooltip>
</svelte:fragment>
<select bind:value={value.aggregation_method}>
<option value="sum">Sum</option>
<option value="mean">Mean</option>
<option value="median">Median</option>
<option value="min">Min</option>
<option value="max">Max</option>
</select>
</Label>
{#if value.type === 'scatter' && value.extraOptions}
<Label label="Mode">
<select bind:value={value.extraOptions.mode}>
<option value="markers">Markers</option>
<option value="lines">Lines</option>
<option value="lines+markers">Lines + Markers</option>
</select>
</Label>
{/if}
<Label label="Tooltip">
<input type="text" bind:value={value.tooltip} />
</Label>
<Label label="Aggregation method">
<svelte:fragment slot="header">
<Tooltip>
A method to aggregate the data. For example, if you have multiple x data points with
the same value, you can choose to sum them up or take the mean. If you don't have
multiple x data points with the same value, this option will have no effect.
</Tooltip>
</svelte:fragment>
<select bind:value={value.aggregation_method}>
<option value="sum">Sum</option>
<option value="mean">Mean</option>
<option value="median">Median</option>
<option value="min">Min</option>
<option value="max">Max</option>
</select>
</Label>
<Label label="Color">
<ColorInput bind:value={value.color} />
</Label>
<Label label="Tooltip">
<input type="text" bind:value={value.tooltip} />
</Label>
<InputsSpecEditor
key={'Data'}
bind:componentInput={value.value}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.value?.['fieldType']}
subFieldType={value.value?.['subFieldType']}
format={value.value?.['format']}
selectOptions={value.value?.['selectOptions']}
tooltip={value.value?.['tooltip']}
fileUpload={value.value?.['fileUpload']}
placeholder={value.value?.['placeholder']}
customTitle={value.value?.['customTitle']}
displayType={false}
/>
<Label label="Color">
<ColorInput bind:value={value.color} />
</Label>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</Popup>
<InputsSpecEditor
key={'Data'}
bind:componentInput={value.value}
id={$selectedComponent?.[0] ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={value.value?.['fieldType']}
subFieldType={value.value?.['subFieldType']}
format={value.value?.['format']}
selectOptions={value.value?.['selectOptions']}
tooltip={value.value?.['tooltip']}
fileUpload={value.value?.['fileUpload']}
placeholder={value.value?.['placeholder']}
customTitle={value.value?.['customTitle']}
displayType={false}
on:openConnection={() => {
closeOnOutsideClick = false
}}
on:closeConnection={() => {
closeOnOutsideClick = true
}}
/>
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
</Popover>
@@ -3,7 +3,8 @@
import PanelSection from '../apps/editor/settingsPanel/common/PanelSection.svelte'
import InputsSpecEditor from '../apps/editor/settingsPanel/InputsSpecEditor.svelte'
import type { BaseAppComponent, RichConfiguration } from '../apps/types'
import { Button, Popup } from '../common'
import { Button } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import type {
@@ -18,77 +19,79 @@
| undefined
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeButton
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<div class="w-96">
<PanelSection
title={`Manage actions programmatically`}
tooltip="
<svelte:fragment slot="content">
<div class="w-96">
<PanelSection
title={`Manage actions programmatically`}
tooltip="
You can manage the order of the actions programmatically: You need to return an array of action ids in the order you want them to appear in the table. You can also hide actions by not including them in the array."
>
<div class="w-full flex gap-2 flex-col mt-2">
{#if actionsOrder}
<InputsSpecEditor
key={'Order'}
bind:componentInput={actionsOrder}
id={selectedId ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={actionsOrder?.['fieldType']}
subFieldType={actionsOrder?.['subFieldType']}
format={actionsOrder?.['format']}
selectOptions={actionsOrder?.['selectOptions']}
tooltip={actionsOrder?.['tooltip']}
fileUpload={actionsOrder?.['fileUpload']}
placeholder={actionsOrder?.['placeholder']}
customTitle={actionsOrder?.['customTitle']}
allowTypeChange={false}
displayType={false}
/>
<Button
size="xs"
color="light"
startIcon={{
icon: Code
}}
variant="border"
on:click={() => {
actionsOrder = undefined
}}
>
Disable
</Button>
{:else}
<Button
size="xs"
color="light"
startIcon={{
icon: Code
}}
variant="border"
on:click={() => {
actionsOrder = {
fieldType: 'text',
type: 'evalv2',
expr: JSON.stringify(components?.map((x) => x.id) ?? []),
connections: []
}
}}
>
Enable
</Button>
{/if}
</div>
</PanelSection>
</div>
</Popup>
>
<div class="w-full flex gap-2 flex-col mt-2">
{#if actionsOrder}
<InputsSpecEditor
key={'Order'}
bind:componentInput={actionsOrder}
id={selectedId ?? ''}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={actionsOrder?.['fieldType']}
subFieldType={actionsOrder?.['subFieldType']}
format={actionsOrder?.['format']}
selectOptions={actionsOrder?.['selectOptions']}
tooltip={actionsOrder?.['tooltip']}
fileUpload={actionsOrder?.['fileUpload']}
placeholder={actionsOrder?.['placeholder']}
customTitle={actionsOrder?.['customTitle']}
allowTypeChange={false}
displayType={false}
/>
<Button
size="xs"
color="light"
startIcon={{
icon: Code
}}
variant="border"
on:click={() => {
actionsOrder = undefined
}}
>
Disable
</Button>
{:else}
<Button
size="xs"
color="light"
startIcon={{
icon: Code
}}
variant="border"
on:click={() => {
actionsOrder = {
fieldType: 'text',
type: 'evalv2',
expr: JSON.stringify(components?.map((x) => x.id) ?? []),
connections: []
}
}}
>
Enable
</Button>
{/if}
</div>
</PanelSection>
</div>
</svelte:fragment>
</Popover>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Popup } from '../common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Alert from '../common/alert/Alert.svelte'
import Label from '../Label.svelte'
import Toggle from '../Toggle.svelte'
@@ -12,61 +12,66 @@
}
</script>
<Popup
<Popover
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
closeButton
closeOnOtherPopoverOpen
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<div class="flex flex-col w-96 p-2 gap-4">
<span class="text-sm mb-2 leading-6 font-semibold">
Table Column
<Tooltip documentationLink="https://www.ag-grid.com/javascript-data-grid/column-definitions/">
Column definitions are used to define columns in ag-Grid.
</Tooltip>
</span>
<svelte:fragment slot="content">
<div class="flex flex-col w-96 p-4 gap-4">
<span class="text-sm mb-2 leading-6 font-semibold">
Table Column
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-definitions/"
>
Column definitions are used to define columns in ag-Grid.
</Tooltip>
</span>
<Label label="Header name">
<input placeholder="header name" bind:value={column.headerName} />
</Label>
<Label label="Header name">
<input placeholder="header name" bind:value={column.headerName} />
</Label>
<Label label="Show column">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide column' }}
bind:checked={column.hideColumn}
size="xs"
/>
</Label>
<Label label="Show column">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide column' }}
bind:checked={column.hideColumn}
size="xs"
/>
</Label>
<Label label="Type">
<select bind:value={column.type}>
<option value="text">Text</option>
<option value="badge">Badge</option>
<option value="link">Link</option>
</select>
</Label>
<Label label="Type">
<select bind:value={column.type}>
<option value="text">Text</option>
<option value="badge">Badge</option>
<option value="link">Link</option>
</select>
</Label>
{#if column.type === 'link'}
<Alert type="info" title="Label" size="xs">
They are two ways to define a link:
<ul class="list-disc list-inside">
<li>
<strong>String</strong>: The string will be used as the link and the label.
</li>
<li>
<strong>Object</strong>: The object must have a <code>href</code> and a
<code>label</code> property.
</li>
</ul>
</Alert>
{/if}
</div>
</Popup>
{#if column.type === 'link'}
<Alert type="info" title="Label" size="xs">
They are two ways to define a link:
<ul class="list-disc list-inside">
<li>
<strong>String</strong>: The string will be used as the link and the label.
</li>
<li>
<strong>Object</strong>: The object must have a <code>href</code> and a
<code>label</code> property.
</li>
</ul>
</Alert>
{/if}
</div>
</svelte:fragment>
</Popover>
+1 -1
View File
@@ -1120,7 +1120,7 @@ export function conditionalMelt(node: HTMLElement, meltItem: AnyMeltElement | un
export type Item = {
displayName: string
action?: (e: CustomEvent<any>) => void
action?: (e: MouseEvent) => void
icon?: any
iconColor?: string
href?: string
+3 -1
View File
@@ -5,8 +5,10 @@ export const zIndexes = {
colorInput: 1002,
disposables: 1100, // Modals and Drawers
svelteSelectOptions: 5000,
popover: 5001,
contextMenu: 6000,
draggingComponent: 10000,
monacoEditor: 10000,
monacoEditorSuggestions: 10001
monacoEditorSuggestions: 10001,
tooltip: 20000
}
@@ -7,7 +7,8 @@
import FolderEditor from '$lib/components/FolderEditor.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { Button, Drawer, DrawerContent, Popup, Skeleton } from '$lib/components/common'
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import FolderInfo from '$lib/components/FolderInfo.svelte'
import FolderUsageInfo from '$lib/components/FolderUsageInfo.svelte'
import { sendUserToast } from '$lib/utils'
@@ -91,18 +92,17 @@
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
>
<div class="flex flex-row">
<Popup
let:close
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
contentClasses="flex flex-col gap-2 p-4"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New folder</Button>
</svelte:fragment>
<div class="flex flex-col gap-2">
<svelte:fragment slot="content" let:close>
<input
class="mr-2"
on:keyup={(e) => handleKeyUp(e, () => close(null))}
on:keyup={(e) => handleKeyUp(e, () => close())}
placeholder="New folder name"
bind:value={newFolderName}
/>
@@ -114,14 +114,14 @@
disabled={!newFolderName}
on:click={() => {
addFolder()
close(null)
close()
}}
>
Create
</Button>
</div>
</div>
</Popup>
</svelte:fragment>
</Popover>
</div>
</PageHeader>
@@ -4,7 +4,8 @@
import { GroupService } from '$lib/gen'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button, Drawer, DrawerContent, Popup, Skeleton } from '$lib/components/common'
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
import GroupEditor from '$lib/components/GroupEditor.svelte'
import GroupInfo from '$lib/components/GroupInfo.svelte'
@@ -40,11 +41,12 @@
}
}
function handleKeyUp(event: KeyboardEvent) {
function handleKeyUp(event: KeyboardEvent, close: () => void) {
const key = event.key
if (key === 'Enter') {
event.preventDefault()
addGroup()
close()
}
}
async function addGroup() {
@@ -87,34 +89,35 @@
>
<div class="flex flex-row">
<div>
<Popup
let:close
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New&nbsp;group</Button>
</svelte:fragment>
<div class="flex-col flex gap-2">
<input
class="mr-2"
on:keyup={handleKeyUp}
placeholder="New group name"
bind:value={newGroupName}
/>
<Button
size="md"
startIcon={{ icon: Plus }}
disabled={!newGroupName}
on:click={() => {
addGroup()
close(null)
}}
>
Create
</Button>
</div>
</Popup>
<svelte:fragment slot="content" let:close>
<div class="flex-col flex gap-2 p-4">
<input
class="mr-2"
on:keyup={(e) => handleKeyUp(e, close)}
placeholder="New group name"
bind:value={newGroupName}
/>
<Button
size="md"
startIcon={{ icon: Plus }}
disabled={!newGroupName}
on:click={() => {
addGroup()
close()
}}
>
Create
</Button>
</div>
</svelte:fragment>
</Popover>
</div>
</div>
</PageHeader>
@@ -168,7 +171,8 @@
displayName: 'Manage group',
icon: Pen,
disabled: !canWrite,
action: () => {
action: (e) => {
e?.stopPropagation()
editGroupName = name
groupDrawer.openDrawer()
}
@@ -3,7 +3,8 @@
import { GroupService } from '$lib/gen'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button, Drawer, DrawerContent, Popup } from '$lib/components/common'
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import InstanceGroupEditor from '$lib/components/InstanceGroupEditor.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
@@ -48,29 +49,30 @@
<PageHeader title="Instance Groups">
<div class="flex flex-row">
<div>
<Popup
let:close
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<svelte:fragment slot="trigger">
<Button size="md" startIcon={{ icon: Plus }} nonCaptureEvent>New&nbsp;group</Button>
</svelte:fragment>
<div class="flex-col flex gap-2">
<input class="mr-2" placeholder="New instance group name" bind:value={newGroupName} />
<Button
size="md"
startIcon={{ icon: Plus }}
disabled={!newGroupName}
on:click={() => {
addInstanceGroup()
close(null)
}}
>
Create
</Button>
</div>
</Popup>
<svelte:fragment slot="content" let:close>
<div class="flex-col flex gap-2 p-4">
<input class="mr-2" placeholder="New instance group name" bind:value={newGroupName} />
<Button
size="md"
startIcon={{ icon: Plus }}
disabled={!newGroupName}
on:click={() => {
addInstanceGroup()
close()
}}
>
Create
</Button>
</div>
</svelte:fragment>
</Popover>
</div>
</div>
</PageHeader>
@@ -60,22 +60,14 @@
import HighlightCode from '$lib/components/HighlightCode.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import LogViewer from '$lib/components/LogViewer.svelte'
import {
ActionRow,
Button,
Popup,
Skeleton,
Tab,
Alert,
DrawerContent
} from '$lib/components/common'
import { ActionRow, Button, Skeleton, Tab, Alert, DrawerContent } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import FlowMetadata from '$lib/components/FlowMetadata.svelte'
import JobArgs from '$lib/components/JobArgs.svelte'
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { goto } from '$lib/navigation'
import { sendUserToast } from '$lib/toast'
import { forLater } from '$lib/forLater'
@@ -90,7 +82,7 @@
import Toggle from '$lib/components/Toggle.svelte'
import WorkflowTimeline from '$lib/components/WorkflowTimeline.svelte'
import ScheduleEditor from '$lib/components/ScheduleEditor.svelte'
import Popover from '$lib/components/Popover.svelte'
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
import HighlightTheme from '$lib/components/HighlightTheme.svelte'
import PreprocessedArgsDisplay from '$lib/components/runs/PreprocessedArgsDisplay.svelte'
import ExecutionDuration from '$lib/components/ExecutionDuration.svelte'
@@ -595,8 +587,11 @@
</Badge>
</Button>
{:else}
<Popup floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}>
<svelte:fragment slot="button">
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-start' }}
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
<Button
title={`Re-start this flow from step ${selectedJobStep} (included). ${
!$enterpriseLicense
@@ -615,46 +610,48 @@
</Badge>
</Button>
</svelte:fragment>
<label class="block text-primary">
<div class="pb-1 text-sm text-secondary"
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
>
<div class="flex w-full">
{#if selectedJobStepType === 'forloop'}
<input
type="number"
min="0"
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
/>
{:else}
<select
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
>
{#each restartBranchNames as [branchIdx, branchName]}
<option value={branchIdx}>{branchName}</option>
{/each}
</select>
{/if}
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Restart flow"
on:click|once={() => {
restartFlow(job?.id, selectedJobStep, branchOrIterationN)
}}
<svelte:fragment slot="content">
<label class="block text-primary">
<div class="pb-1 text-sm text-secondary"
>{selectedJobStepType == 'forloop' ? 'From iteration #:' : 'From branch:'}</div
>
<ArrowRight size={18} />
</Button>
</div>
</label>
</Popup>
<div class="flex w-full">
{#if selectedJobStepType === 'forloop'}
<input
type="number"
min="0"
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
/>
{:else}
<select
bind:value={branchOrIterationN}
class="!w-32 grow"
on:click|stopPropagation={() => {}}
>
{#each restartBranchNames as [branchIdx, branchName]}
<option value={branchIdx}>{branchName}</option>
{/each}
</select>
{/if}
<Button
size="xs"
color="blue"
buttonType="button"
btnClasses="!p-1 !w-[34px] !ml-1"
aria-label="Restart flow"
on:click|once={() => {
restartFlow(job?.id, selectedJobStep, branchOrIterationN)
}}
>
<ArrowRight size={18} />
</Button>
</div>
</label>
</svelte:fragment>
</Popover>
{/if}
{/if}
{#if job?.job_kind === 'script' || job?.job_kind === 'flow'}
@@ -762,13 +759,17 @@
</div>
{/if}
{#if !job.visible_to_owner}
<div
><Badge color="red"
>only visible to you <Tooltip
>The option to hide this run from the owner of this script or flow was activated</Tooltip
>
</Badge></div
>
<div>
<Badge color="red">
only visible to you
<Tooltip>
<svelte:fragment slot="text">
The option to hide this run from the owner of this script or flow was
activated
</svelte:fragment>
</Tooltip>
</Badge>
</div>
{/if}
{#if job?.['labels'] && Array.isArray(job?.['labels']) && job?.['labels'].length > 0}
{#each job?.['labels'] as label}
@@ -779,7 +780,7 @@
{/if}
{#if concurrencyKey}
<div>
<Popover notClickable>
<Tooltip notClickable>
<svelte:fragment slot="text">
This job has concurrency limits enabled with the key
<a
@@ -793,7 +794,7 @@
>
<Badge>Concurrency: {truncateRev(concurrencyKey, 20)}</Badge></a
>
</Popover>
</Tooltip>
</div>
{/if}
</div>
@@ -1,7 +1,8 @@
<script lang="ts">
import AssignableTags from '$lib/components/AssignableTags.svelte'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button, Popup, Skeleton, Tab, Tabs } from '$lib/components/common'
import { Button, Skeleton, Tab, Tabs } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
@@ -15,7 +16,13 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import WorkspaceGroup from '$lib/components/WorkerGroup.svelte'
import { WorkerService, type WorkerPing, ConfigService, SettingService } from '$lib/gen'
import { enterpriseLicense, superadmin, userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import {
enterpriseLicense,
superadmin,
userStore,
workspaceStore,
userWorkspaces
} from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { displayDate, groupBy, pluralize, truncate } from '$lib/utils'
import { AlertTriangle, LineChart, List, Plus, Search } from 'lucide-svelte'
@@ -272,167 +279,173 @@
</DrawerContent>
</Drawer>
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.workers}
<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?.workers}
<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="Workers"
tooltip="The workers are the dutiful servants that execute the jobs."
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
>
{#if $superadmin}
<div class="flex flex-row-reverse w-full pb-2 items-center gap-4">
<div>
<AssignableTags
showWorkspaceRestriction
on:refresh={() => {
loadCustomTags()
}}
/>
</div>
<div>
<DefaultTags bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
</div>
<div>
<Button
size="xs"
color="dark"
startIcon={{
icon: LineChart
}}
on:click={() => {
queueMetricsDrawer?.openDrawer()
}}
>
Queue metrics
</Button>
</div>
<div>
<Button
size="xs"
color="dark"
startIcon={{
icon: List
}}
on:click={() => {
openSearchWithPrefilledText('!')
}}
>
Service logs
</Button>
</div>
</div>
{/if}
</PageHeader>
{#if workers != undefined}
{#if groupedWorkers.length == 0}
<p>No workers seem to be available</p>
{/if}
<div class="pt-4 pb-8 w-full flex justify-between items-center"
><h4
>{groupWorkers?.length} Worker Groups <Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
>Worker groups are groups of workers that share a config and are meant to be identical.
Worker groups are meant to be used with tags. Tags can be assigned to scripts and flows
and can be seen as dedicated queues. Only the corresponding
</Tooltip></h4
>
<div />
{#if $superadmin}
<div class="flex flex-row items-center">
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="button">
<div class="flex items-center gap-2">
<Button
size="sm"
startIcon={{ icon: Plus }}
nonCaptureEvent
disabled={!$enterpriseLicense}
dropdownItems={$enterpriseLicense
? [
{
label: 'Copy groups config as YAML',
onClick: () => {
if (!workerGroups) {
return sendUserToast('No worker groups found', true)
}
const workersConfig = Object.entries(workerGroups).map(
([name, config]) => ({
name,
...config
})
)
navigator.clipboard.writeText(YAML.stringify(workersConfig))
sendUserToast('Worker groups config copied to clipboard as YAML')
}
},
{
label: 'Import groups config from YAML',
onClick: () => {
importConfigDrawer?.toggleDrawer?.()
}
}
]
: undefined}
>
<span class="hidden md:block"
>New group config {!$enterpriseLicense ? '(EE)' : ''}</span
>
<Tooltip light>
Worker Group configs are propagated to every workers in the worker group
</Tooltip>
</Button>
</div>
</svelte:fragment>
<div class="flex flex-col gap-2">
<input class="mr-2 h-full" placeholder="New group name" bind:value={newConfigName} />
{#if !$enterpriseLicense}
<div class="flex items-center whitespace-nowrap text-yellow-600 gap-2">
<AlertTriangle size={16} />
EE only
</div>
{/if}
<Button
size="sm"
startIcon={{ icon: Plus }}
disabled={!newConfigName || !$enterpriseLicense}
on:click={addConfig}
>
Create
</Button>
</div>
</Popup>
</div>
{/if}</div
<CenteredPage>
<PageHeader
title="Workers"
tooltip="The workers are the dutiful servants that execute the jobs."
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
>
{#if $superadmin}
<div class="flex flex-row-reverse w-full pb-2 items-center gap-4">
<div>
<AssignableTags
showWorkspaceRestriction
on:refresh={() => {
loadCustomTags()
}}
/>
</div>
<div>
<DefaultTags bind:defaultTagPerWorkspace bind:defaultTagWorkspaces />
</div>
<div>
<Button
size="xs"
color="dark"
startIcon={{
icon: LineChart
}}
on:click={() => {
queueMetricsDrawer?.openDrawer()
}}
>
Queue metrics
</Button>
</div>
<div>
<Button
size="xs"
color="dark"
startIcon={{
icon: List
}}
on:click={() => {
openSearchWithPrefilledText('!')
}}
>
Service logs
</Button>
</div>
</div>
{/if}
</PageHeader>
{#if (groupedWorkers ?? []).length > 5}
<div class="flex gap-2 items-center">
<div class="text-secondary text-sm">Worker group:</div>
<AutoComplete
noInputStyles
items={groupedWorkers.map((x) => x[0])}
bind:selectedItem={selectedTab}
hideArrow={true}
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary"'}
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
className="!font-gray-600 !font-primary !bg-surface-primary"
/>
{#if workers != undefined}
{#if groupedWorkers.length == 0}
<p>No workers seem to be available</p>
{/if}
<!-- <select
<div class="pt-4 pb-8 w-full flex justify-between items-center"
><h4
>{groupWorkers?.length} Worker Groups <Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
>Worker groups are groups of workers that share a config and are meant to be identical.
Worker groups are meant to be used with tags. Tags can be assigned to scripts and flows
and can be seen as dedicated queues. Only the corresponding
</Tooltip></h4
>
<div />
{#if $superadmin}
<div class="flex flex-row items-center">
<Popover
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
>
<svelte:fragment slot="trigger">
<div class="flex items-center gap-2">
<Button
size="sm"
startIcon={{ icon: Plus }}
nonCaptureEvent
disabled={!$enterpriseLicense}
dropdownItems={$enterpriseLicense
? [
{
label: 'Copy groups config as YAML',
onClick: () => {
if (!workerGroups) {
return sendUserToast('No worker groups found', true)
}
const workersConfig = Object.entries(workerGroups).map(
([name, config]) => ({
name,
...config
})
)
navigator.clipboard.writeText(YAML.stringify(workersConfig))
sendUserToast('Worker groups config copied to clipboard as YAML')
}
},
{
label: 'Import groups config from YAML',
onClick: () => {
importConfigDrawer?.toggleDrawer?.()
}
}
]
: undefined}
>
<span class="hidden md:block"
>New group config {!$enterpriseLicense ? '(EE)' : ''}</span
>
<Tooltip light>
Worker Group configs are propagated to every workers in the worker group
</Tooltip>
</Button>
</div>
</svelte:fragment>
<svelte:fragment slot="content">
<div class="flex flex-col gap-2 p-4">
<input
class="mr-2 h-full"
placeholder="New group name"
bind:value={newConfigName}
/>
{#if !$enterpriseLicense}
<div class="flex items-center whitespace-nowrap text-yellow-600 gap-2">
<AlertTriangle size={16} />
EE only
</div>
{/if}
<Button
size="sm"
startIcon={{ icon: Plus }}
disabled={!newConfigName || !$enterpriseLicense}
on:click={addConfig}
>
Create
</Button>
</div>
</svelte:fragment>
</Popover>
</div>
{/if}</div
>
{#if (groupedWorkers ?? []).length > 5}
<div class="flex gap-2 items-center">
<div class="text-secondary text-sm">Worker group:</div>
<AutoComplete
noInputStyles
items={groupedWorkers.map((x) => x[0])}
bind:selectedItem={selectedTab}
hideArrow={true}
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary"'}
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
className="!font-gray-600 !font-primary !bg-surface-primary"
/>
<!-- <select
class="max-w-64"
bind:value={selectedTab}
on:change={() => {
@@ -448,221 +461,226 @@
</option>
{/each}
</select> -->
</div>
{:else}
<Tabs bind:selected={selectedTab}>
{#each groupedWorkers.map((x) => x[0]) as name (name)}
{@const worker_group = groupedWorkers.find((x) => x[0] == name)}
{#if worker_group}
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
)}
<Tab value={worker_group[0]}>
{`${worker_group[0]} - ${pluralize(activeWorkers?.length, 'worker')}`}
<Tooltip>Number of workers active in the last 15s</Tooltip>
</Tab>
{:else}
<Tab value={name}>
{name} (0 worker)
</Tab>
{/if}
{/each}
</Tabs>
{/if}
<div>
{#if worker_group}
{@const config = (workerGroups ?? {})[worker_group[0]]}
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
)}
<WorkspaceGroup
{customTags}
name={worker_group[0]}
workers={worker_group[1]}
{config}
on:reload={() => {
loadWorkerGroups()
}}
activeWorkers={activeWorkers?.length ?? 0}
{defaultTagPerWorkspace}
/>
<div class="flex flex-row items-center gap-2 relative my-2">
<input
class="max-w-80 border rounded-md !pl-8"
placeholder="Search workers by name..."
autocomplete="off"
bind:value={search}
/>
<Search class="absolute left-2 " size={14} />
</div>
{#if worker_group?.[1].length == 0 && search}
<div class="text-xs text-tertiary">
No workers found. Reset the search to see all workers.
</div>
{:else}
<DataTable>
<Head>
<tr>
<Cell head first>Worker</Cell>
<Cell head>
<div class="flex flex-row items-center gap-1">
Worker Tags
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups#assign-custom-worker-groups"
>
If defined, the workers only pull jobs with the same corresponding tag
</Tooltip>
</div>
</Cell>
<Cell head>Last ping</Cell>
<Cell head>Worker start</Cell>
<Cell head>Jobs ran</Cell>
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
<Cell head>Last job</Cell>
<Cell head>Occupancy rate<br />(15s/5m/30m/ever)</Cell>
{/if}
<Cell head>Memory usage<br />(Windmill)</Cell>
<Cell head>Limits</Cell>
<Cell head>Version</Cell>
<Cell head last>Liveness</Cell>
</tr>
</Head>
<tbody class="divide-y">
{#each worker_group[1] as [section, workers]}
<tr class="border-t">
<Cell
first
colspan={(!config || config?.dedicated_worker == undefined) && $superadmin
? 11
: 9}
scope="colgroup"
class="bg-surface-secondary/30 !py-1 border-b !text-xs"
>
<div class="flex flex-row w-full">
<div class="min-w-64">
Host:
<span class="font-semibold">{section?.split(splitter)?.[0]}</span>
</div>
<span class="ml-4">IP: </span>
<span class="font-semibold">{workers[0].ip}</span>
{#if workers?.length > 1}
<span class="font-semibold ml-8">{workers?.length} Workers</span>
{/if}
</div>
</Cell>
</tr>
{#if workers}
{#each workers as { worker, custom_tags, last_ping, started_at, jobs_executed, last_job_id, last_job_workspace_id, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, occupancy_rate, wm_version, vcpus, memory, memory_usage, wm_memory_usage }}
<tr>
<Cell first>{worker}</Cell>
<Cell>
{#if custom_tags && custom_tags?.length > 2}
{truncate(custom_tags?.join(', ') ?? '', 10)}
<Tooltip>{custom_tags?.join(', ')}</Tooltip>
{:else}
{custom_tags?.join(', ') ?? ''}
{/if}
</Cell>
<Cell>{last_ping != undefined ? last_ping + timeSinceLastPing : -1}s ago</Cell
>
<Cell>{displayDate(started_at)}</Cell>
<Cell>{jobs_executed}</Cell>
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
<Cell>
{#if last_job_id}
<a href={`/run/${last_job_id}?workspace=${last_job_workspace_id}`}>
View last job
</a>
<br />
(workspace {last_job_workspace_id})
{/if}
</Cell>
<Cell>
{displayOccupancyRate(occupancy_rate_15s)}/{displayOccupancyRate(
occupancy_rate_5m
)}/{displayOccupancyRate(occupancy_rate_30m)}/{displayOccupancyRate(
occupancy_rate
)}
</Cell>
{/if}
<Cell>
<div class="flex flex-col gap-1">
<div>
{memory_usage ? Math.round(memory_usage / 1024 / 1024) + 'MB' : '--'}
</div>
<div>
({wm_memory_usage
? Math.round(wm_memory_usage / 1024 / 1024) + 'MB'
: '--'})
</div>
</div>
</Cell>
<Cell>
<div class="flex flex-col gap-1">
<div>
{vcpus ? (vcpus / 100000).toFixed(2) + ' vCPUs' : '--'}
</div>
<div>
{memory ? Math.round(memory / 1024 / 1024) + 'MB' : '--'}
</div>
</div>
</Cell>
<Cell>
<div class="!text-2xs">
{wm_version.split('-')[0]}<Tooltip>{wm_version}</Tooltip>
</div>
</Cell>
<Cell last>
<Badge
color={last_ping != undefined
? last_ping < 60
? 'green'
: 'red'
: 'gray'}
>
{last_ping != undefined ? (last_ping < 60 ? 'Alive' : 'Dead') : 'Unknown'}
</Badge>
</Cell>
</tr>
{/each}
{/if}
{/each}
</tbody>
</DataTable>
{/if}
{:else}
{@const worker_group = Object.entries(workerGroups ?? {})
.filter((x) => !groupedWorkers.some((y) => y[0] == x[0]))
.find((x) => x[0] == selectedTab)}
<Tabs bind:selected={selectedTab}>
{#each groupedWorkers.map((x) => x[0]) as name (name)}
{@const worker_group = groupedWorkers.find((x) => x[0] == name)}
{#if worker_group}
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
)}
<Tab value={worker_group[0]}>
{`${worker_group[0]} - ${pluralize(activeWorkers?.length, 'worker')}`}
<Tooltip>Number of workers active in the last 15s</Tooltip>
</Tab>
{:else}
<Tab value={name}>
{name} (0 worker)
</Tab>
{/if}
{/each}
</Tabs>
{/if}
<div>
{#if worker_group}
{@const config = (workerGroups ?? {})[worker_group[0]]}
{@const activeWorkers = worker_group?.[1].flatMap((x) =>
x[1]?.filter((y) => (y.last_ping ?? 0) < 15)
)}
<WorkspaceGroup
{customTags}
name={worker_group[0]}
workers={worker_group[1]}
{config}
on:reload={() => {
loadWorkerGroups()
}}
name={worker_group[0]}
config={worker_group[1]}
activeWorkers={0}
activeWorkers={activeWorkers?.length ?? 0}
{defaultTagPerWorkspace}
/>
<div class="text-xs text-tertiary"> No workers currently in this worker group </div>
<div class="flex flex-row items-center gap-2 relative my-2">
<input
class="max-w-80 border rounded-md !pl-8"
placeholder="Search workers by name..."
autocomplete="off"
bind:value={search}
/>
<Search class="absolute left-2 " size={14} />
</div>
{#if worker_group?.[1].length == 0 && search}
<div class="text-xs text-tertiary">
No workers found. Reset the search to see all workers.
</div>
{:else}
<DataTable>
<Head>
<tr>
<Cell head first>Worker</Cell>
<Cell head>
<div class="flex flex-row items-center gap-1">
Worker Tags
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups#assign-custom-worker-groups"
>
If defined, the workers only pull jobs with the same corresponding tag
</Tooltip>
</div>
</Cell>
<Cell head>Last ping</Cell>
<Cell head>Worker start</Cell>
<Cell head>Jobs ran</Cell>
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
<Cell head>Last job</Cell>
<Cell head>Occupancy rate<br />(15s/5m/30m/ever)</Cell>
{/if}
<Cell head>Memory usage<br />(Windmill)</Cell>
<Cell head>Limits</Cell>
<Cell head>Version</Cell>
<Cell head last>Liveness</Cell>
</tr>
</Head>
<tbody class="divide-y">
{#each worker_group[1] as [section, workers]}
<tr class="border-t">
<Cell
first
colspan={(!config || config?.dedicated_worker == undefined) && $superadmin
? 11
: 9}
scope="colgroup"
class="bg-surface-secondary/30 !py-1 border-b !text-xs"
>
<div class="flex flex-row w-full">
<div class="min-w-64">
Host:
<span class="font-semibold">{section?.split(splitter)?.[0]}</span>
</div>
<span class="ml-4">IP: </span>
<span class="font-semibold">{workers[0].ip}</span>
{#if workers?.length > 1}
<span class="font-semibold ml-8">{workers?.length} Workers</span>
{/if}
</div>
</Cell>
</tr>
{#if workers}
{#each workers as { worker, custom_tags, last_ping, started_at, jobs_executed, last_job_id, last_job_workspace_id, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, occupancy_rate, wm_version, vcpus, memory, memory_usage, wm_memory_usage }}
<tr>
<Cell first>{worker}</Cell>
<Cell>
{#if custom_tags && custom_tags?.length > 2}
{truncate(custom_tags?.join(', ') ?? '', 10)}
<Tooltip>{custom_tags?.join(', ')}</Tooltip>
{:else}
{custom_tags?.join(', ') ?? ''}
{/if}
</Cell>
<Cell
>{last_ping != undefined ? last_ping + timeSinceLastPing : -1}s ago</Cell
>
<Cell>{displayDate(started_at)}</Cell>
<Cell>{jobs_executed}</Cell>
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
<Cell>
{#if last_job_id}
<a href={`/run/${last_job_id}?workspace=${last_job_workspace_id}`}>
View last job
</a>
<br />
(workspace {last_job_workspace_id})
{/if}
</Cell>
<Cell>
{displayOccupancyRate(occupancy_rate_15s)}/{displayOccupancyRate(
occupancy_rate_5m
)}/{displayOccupancyRate(occupancy_rate_30m)}/{displayOccupancyRate(
occupancy_rate
)}
</Cell>
{/if}
<Cell>
<div class="flex flex-col gap-1">
<div>
{memory_usage ? Math.round(memory_usage / 1024 / 1024) + 'MB' : '--'}
</div>
<div>
({wm_memory_usage
? Math.round(wm_memory_usage / 1024 / 1024) + 'MB'
: '--'})
</div>
</div>
</Cell>
<Cell>
<div class="flex flex-col gap-1">
<div>
{vcpus ? (vcpus / 100000).toFixed(2) + ' vCPUs' : '--'}
</div>
<div>
{memory ? Math.round(memory / 1024 / 1024) + 'MB' : '--'}
</div>
</div>
</Cell>
<Cell>
<div class="!text-2xs">
{wm_version.split('-')[0]}<Tooltip>{wm_version}</Tooltip>
</div>
</Cell>
<Cell last>
<Badge
color={last_ping != undefined
? last_ping < 60
? 'green'
: 'red'
: 'gray'}
>
{last_ping != undefined
? last_ping < 60
? 'Alive'
: 'Dead'
: 'Unknown'}
</Badge>
</Cell>
</tr>
{/each}
{/if}
{/each}
</tbody>
</DataTable>
{/if}
{:else}
{@const worker_group = Object.entries(workerGroups ?? {})
.filter((x) => !groupedWorkers.some((y) => y[0] == x[0]))
.find((x) => x[0] == selectedTab)}
{#if worker_group}
<WorkspaceGroup
{customTags}
workers={worker_group[1]}
on:reload={() => {
loadWorkerGroups()
}}
name={worker_group[0]}
config={worker_group[1]}
activeWorkers={0}
/>
<div class="text-xs text-tertiary"> No workers currently in this worker group </div>
{/if}
{/if}
{/if}
</div>
<div class="pb-20" />
<AutoscalingEvents worker_group={selectedTab} />
{:else}
<div class="flex flex-col">
{#each new Array(4) as _}
<Skeleton layout={[[8], 1]} />
{/each}
</div>
{/if}
</CenteredPage>
</div>
<div class="pb-20" />
<AutoscalingEvents worker_group={selectedTab} />
{:else}
<div class="flex flex-col">
{#each new Array(4) as _}
<Skeleton layout={[[8], 1]} />
{/each}
</div>
{/if}
</CenteredPage>
{/if}