mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
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:
@@ -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 <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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 <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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
+21
-17
@@ -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>
|
||||
|
||||
+11
-7
@@ -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,
|
||||
|
||||
+53
-51
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 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 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}
|
||||
|
||||
Reference in New Issue
Block a user