Files
windmill/frontend/src/lib/components/VariableEditor.svelte
T
17b19a576d feat(frontend): migrate toggle to melt (4/4) (#5329)
* use melt menu in sidebar

* stop keyboard navigation for disabled items

* use melt menu for FavoriteMenu and WorkspaceMenu

* fix popover placement for menuButton

* use melt menu for operator menu

* fix notification

* fix operator menu

* Use melt menu in FlowJobsMenu

* use melt menu for AppMenu

* clean code

* clean code

* add use clickOutside option to Menu

* use pointerdown_outside

* use pointerdown_outside

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

* use pointerdown in menus

* add max-h to app dropdown menu

* keep more open in operator menu

* add a MenuItem component

* clean

* nit

* nit

* clean code

* put conditionalMelt as utility function

* remove unused Portal

* Add debounce effect in operator menu

* fix component jumping due to z-index

* format pages

* migrate dropdown to melt

* migrate toggle to melt

* migrate popup to melt popover

* fix missing toggle item

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

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

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

* fix errors in main.rs

* fix tests

* remove nsjail for pip

* fix imports

* fix compilation error

* reinforce melt types

* fix racing condition issue in closing operator menu

* nit

* fix id conflix with melt element

* nit

* clean code

* use melt dropdown instead of menubar

* prevent modal from closing on click outside button in menu

* Apply automatic changes

* fix nit

* nit

* close dropdown when opening a new one

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

* use melt menu in sidebar

* stop keyboard navigation for disabled items

* use melt menu for FavoriteMenu and WorkspaceMenu

* fix popover placement for menuButton

* use melt menu for operator menu

* fix notification

* fix operator menu

* Use melt menu in FlowJobsMenu

* use melt menu for AppMenu

* clean code

* clean code

* add use clickOutside option to Menu

* use pointerdown_outside

* use pointerdown_outside

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

* use pointerdown in menus

* add max-h to app dropdown menu

* keep more open in operator menu

* add a MenuItem component

* clean

* nit

* nit

* clean code

* put conditionalMelt as utility function

* remove unused Portal

* Add debounce effect in operator menu

* fix component jumping due to z-index

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

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

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

* fix errors in main.rs

* fix tests

* remove nsjail for pip

* fix imports

* fix compilation error

* reinforce melt types

* fix racing condition issue in closing operator menu

* nit

* fix id conflix with melt element

* nit

* prevent modal from closing on click outside button in menu

---------

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

* clean

* fix z index and render

* fix initialize of dropdownmenu after melt migration

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

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

* update python parser package

* add local rooting for MenuItem

* fix z index

* clean

* nit

* nit

* clean code

* nit

* nit

* clean code

* reinforce melt types

* wip

* reiforce instance select types for toggleButton

* nit

* fix double event

* fix selectedTable toggle

* fix sqs toggleButton

* fix potential issue with binding in toggleGroup

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

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

---------

Co-authored-by: pyranota <92104930+pyranota@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-02-26 17:32:40 +01:00

253 lines
7.2 KiB
Svelte

<script lang="ts">
import { VariableService } from '$lib/gen'
import Path from './Path.svelte'
import { createEventDispatcher } from 'svelte'
import { userStore, workspaceStore } from '$lib/stores'
import Tooltip from './Tooltip.svelte'
import { Button } from './common'
import Drawer from './common/drawer/Drawer.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Alert from './common/alert/Alert.svelte'
import Toggle from './Toggle.svelte'
import type SimpleEditor from './SimpleEditor.svelte'
import { sendUserToast } from '$lib/toast'
import { canWrite, isOwner } from '$lib/utils'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import { Loader2, Save } from 'lucide-svelte'
import autosize from '$lib/autosize'
const dispatch = createEventDispatcher()
let path: string = ''
let variable: {
value: string
is_secret: boolean
description: string
} = {
value: '',
is_secret: true,
description: ''
}
let valid = true
let drawer: Drawer
let edit = false
let initialPath: string
let pathError = ''
let can_write = true
export function initNew(): void {
variable = {
value: '',
is_secret: true,
description: ''
}
edit = false
initialPath = ''
path = ''
can_write = true
drawer.openDrawer()
}
export async function editVariable(edit_path: string): Promise<void> {
edit = true
const getV = await VariableService.getVariable({
workspace: $workspaceStore ?? '',
path: edit_path,
decryptSecret: false
})
can_write =
getV.workspace_id == $workspaceStore &&
canWrite(edit_path, getV.extra_perms ?? {}, $userStore)
variable = {
value: getV.value ?? '',
is_secret: getV.is_secret,
description: getV.description ?? ''
}
initialPath = edit_path
path = edit_path
drawer.openDrawer()
}
export async function loadVariable(path: string): Promise<void> {
const getV = await VariableService.getVariable({
workspace: $workspaceStore ?? '',
path,
decryptSecret: true
})
variable.value = getV.value ?? ''
editor?.setCode(variable.value)
}
const MAX_VARIABLE_LENGTH = 10000
$: valid = variable.value.length <= MAX_VARIABLE_LENGTH
async function createVariable(): Promise<void> {
await VariableService.createVariable({
workspace: $workspaceStore!,
requestBody: {
path,
value: variable.value,
is_secret: variable.is_secret,
description: variable.description
}
})
sendUserToast(`Created variable ${path}`)
dispatch('create')
drawer.closeDrawer()
}
async function updateVariable(): Promise<void> {
try {
const getV = await VariableService.getVariable({
workspace: $workspaceStore ?? '',
path: initialPath,
decryptSecret: false
})
await VariableService.updateVariable({
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
path: getV.path != path ? path : undefined,
value: variable.value == '' ? undefined : variable.value,
is_secret: getV.is_secret != variable.is_secret ? variable.is_secret : undefined,
description: getV.description != variable.description ? variable.description : undefined
}
})
sendUserToast(`Updated variable ${initialPath}`)
dispatch('create')
drawer.closeDrawer()
} catch (err) {
sendUserToast(`Could not update variable: ${err.body}`, true)
}
}
let editorKind: 'plain' | 'json' | 'yaml' = 'plain'
let editor: SimpleEditor | undefined = undefined
</script>
<Drawer bind:this={drawer} size="900px">
<DrawerContent
title={edit ? `Update variable at ${initialPath}` : 'Add a variable'}
on:close={drawer.closeDrawer}
>
<div class="flex flex-col gap-8">
{#if !can_write}
<Alert type="warning" title="Only read access">
You only have read access to this resource and cannot edit it
</Alert>
{/if}
<Section label="Path">
<div class="flex flex-col gap-4">
<Path
disabled={initialPath != '' && !isOwner(initialPath, $userStore, $workspaceStore)}
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder="variable"
kind="variable"
/>
<Toggle
on:change={() => edit && loadVariable(initialPath)}
bind:checked={variable.is_secret}
disabled={edit && $userStore?.operator}
options={{ right: 'Secret' }}
/>
{#if variable.is_secret}
<Alert type="warning" title="Audit log for each access">
Every secret is encrypted at rest and in transit with a key specific to this
workspace. In addition, any read of a secret variable generates an audit log whose
operation name is: variables.decrypt_secret
</Alert>
{/if}
</div>
</Section>
<Section label="Variable value">
<svelte:fragment slot="header">
<span class="text-sm text-tertiary mr-4 font-normal">
({variable.value.length}/{MAX_VARIABLE_LENGTH} characters)
</span>
</svelte:fragment>
<div>
<div class="mb-1">
{#if edit && variable.is_secret}{#if $userStore?.operator}
<div class="p-2 border">Operators cannot load secret value</div>
{:else}
<Button variant="border" size="xs" on:click={() => loadVariable(initialPath)}
>Load secret value<Tooltip>Will generate an audit log</Tooltip></Button
>{/if}{/if}
</div>
<div class="flex flex-col gap-2">
<ToggleButtonGroup bind:selected={editorKind} let:item>
<ToggleButton value="plain" label="Plain" {item} />
<ToggleButton value="json" label="Json" {item} />
<ToggleButton value="yaml" label="YAML" {item} />
</ToggleButtonGroup>
{#if editorKind == 'plain'}
<textarea
disabled={!can_write}
rows="4"
use:autosize
bind:value={variable.value}
placeholder="Update variable value"
/>
{:else if editorKind == 'json'}
<div class="border rounded mb-4 w-full">
{#await import('$lib/components/SimpleEditor.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
bind:this={editor}
autoHeight
lang="json"
bind:code={variable.value}
fixedOverflowWidgets={false}
/>
{/await}
</div>
{:else if editorKind == 'yaml'}
<div class="border rounded mb-4 w-full">
{#await import('$lib/components/SimpleEditor.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
bind:this={editor}
autoHeight
lang="yaml"
bind:code={variable.value}
fixedOverflowWidgets={false}
/>
{/await}
</div>
{/if}
</div>
</div>
</Section>
<Section label="Description">
<textarea
rows="4"
use:autosize
bind:value={variable.description}
placeholder="Used for X"
/>
</Section>
</div>
<svelte:fragment slot="actions">
<Button
on:click={() => (edit ? updateVariable() : createVariable())}
disabled={!can_write || !valid || pathError != ''}
startIcon={{ icon: Save }}
color="dark"
size="sm"
>
{edit ? 'Update' : 'Save'}
</Button>
</svelte:fragment>
</DrawerContent>
</Drawer>