use scroll for variables page

This commit is contained in:
Guilhem
2025-03-31 17:07:36 +01:00
parent ef4472cfa0
commit 63f259edcf

View File

@@ -37,6 +37,7 @@
EyeOff,
Circle
} from 'lucide-svelte'
import Scroll from '$lib/components/Scroll.svelte'
type ListableVariableW = ListableVariable & { canWrite: boolean }
@@ -150,326 +151,330 @@
<p>Page not available for operators</p>
</div>
{:else}
<CenteredPage>
<PageHeader
title="Variables"
tooltip="Save and permission strings to be reused in Scripts and Flows."
documentationLink="https://www.windmill.dev/docs/core_concepts/variables_and_secrets"
>
<div class="flex flex-row justify-end">
{#if tab == 'contextual' && ($userStore?.is_admin || $userStore?.is_super_admin)}
<Button
size="md"
startIcon={{ icon: Plus }}
on:click={() => contextualVariableEditor.initNew()}
>
New&nbsp;contextual&nbsp;variable
</Button>
{:else}
<Button size="md" startIcon={{ icon: Plus }} on:click={() => variableEditor.initNew()}>
New&nbsp;variable
</Button>
{/if}
</div>
</PageHeader>
<VariableEditor bind:this={variableEditor} on:create={loadVariables} />
<ContextualVariableEditor
bind:this={contextualVariableEditor}
on:update={loadContextualVariables}
/>
<ShareModal
bind:this={shareModal}
on:change={() => {
loadVariables()
}}
/>
<Tabs bind:selected={tab}>
<Tab size="md" value="workspace">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Workspace
<Scroll class="h-full w-full">
<CenteredPage>
<PageHeader
title="Variables"
tooltip="Save and permission strings to be reused in Scripts and Flows."
documentationLink="https://www.windmill.dev/docs/core_concepts/variables_and_secrets"
>
<div class="flex flex-row justify-end">
{#if tab == 'contextual' && ($userStore?.is_admin || $userStore?.is_super_admin)}
<Button
size="md"
startIcon={{ icon: Plus }}
on:click={() => contextualVariableEditor.initNew()}
>
New&nbsp;contextual&nbsp;variable
</Button>
{:else}
<Button size="md" startIcon={{ icon: Plus }} on:click={() => variableEditor.initNew()}>
New&nbsp;variable
</Button>
{/if}
</div>
</Tab>
<Tab size="md" value="contextual">
<div class="flex gap-2 items-center my-1">
<DollarSign size={18} />
Contextual
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/variables_and_secrets#contextual-variables"
>
Contextual variables are passed as environment variables when running a script and
depends on the execution context.
</Tooltip>
</div>
</Tab>
</Tabs>
{#if tab == 'workspace'}
<div class="pt-2">
<input placeholder="Search Variable" bind:value={filter} class="input mt-1" />
</div>
<div class="min-h-[56px]">
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
</div>
<div class="relative overflow-x-auto pb-40 pr-4">
{#if !filteredItems}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(3) as _}
<Skeleton layout={[[3.5], 0.5]} />
{/each}
{:else if filteredItems.length == 0}
<div class="flex flex-col items-center justify-center h-full">
<div class="text-md font-medium">No variables found</div>
<div class="text-sm text-secondary">
Try changing the filters or creating a new variable
</div>
</PageHeader>
<VariableEditor bind:this={variableEditor} on:create={loadVariables} />
<ContextualVariableEditor
bind:this={contextualVariableEditor}
on:update={loadContextualVariables}
/>
<ShareModal
bind:this={shareModal}
on:change={() => {
loadVariables()
}}
/>
<Tabs bind:selected={tab}>
<Tab size="md" value="workspace">
<div class="flex gap-2 items-center my-1">
<Building size={18} />
Workspace
</div>
{:else}
<DataTable size="xs">
<Head>
<tr>
<Cell head first class="!px-0" />
<Cell head>Path</Cell>
<Cell head>Value</Cell>
<Cell head>Description</Cell>
<Cell head />
<Cell head last />
</tr>
</Head>
<tbody class="divide-y">
{#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked }}
<Row>
<Cell class="!px-0 text-center w-12" first>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</Cell>
<Cell>
<a
class="break-all"
id="edit-{path}"
on:click={() => variableEditor.editVariable(path)}
href="#{path}"
>
{#if marked}
{@html marked}
{:else}
{path}
{/if}
</a>
</Cell>
<Cell>
<span class="inline-flex flex-row items-center gap-2">
<div class="text-sm break-words">
{#if value}
{truncate(value, 20)}
{:else}
&lowast;&lowast;&lowast;&lowast;
{/if}
</div>
{#if is_secret}
<Popover notClickable>
<EyeOff size={12} />
<span slot="text">This item is secret</span>
</Popover>
{/if}
</span>
</Cell>
<Cell class="break-words">
<span class="text-xs text-tertiary">{truncate(description ?? '', 50)} </span>
</Cell>
<Cell class="text-center">
<div class="flex flex-row items-center gap-4">
{#if is_linked}
<Popover notClickable>
<Link size={16} />
<div slot="text">
This variable is linked with a resource of the same path. They are
deleted and renamed together.
</div>
</Popover>
{/if}
{#if account}
<Popover notClickable>
<RefreshCw size={16} />
<div slot="text">
This OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
{#if is_refreshed}
<div class="">
{#if refresh_error}
<Popover notClickable>
<div class="relative inline-flex justify-center items-center w-4 h-4">
<Circle
class="text-red-600 animate-ping absolute z-50 w-4 h-4 fill-current"
size={12}
/>
<Circle
class="text-red-600 relative inline-flex fill-current "
size={12}
/>
</div>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover notClickable>
<Circle
class="text-yellow-600 animate-[pulse_5s_linear_infinite] fill-current"
size={12}
/>
<div slot="text">
The access_token is expired, it will get renewed the next time this
variable is fetched or you can request is to be refreshed in the
dropdown on the right.
</div>
</Popover>
{:else}
<Popover notClickable>
<Circle
class="text-green-600 animate-[pulse_5s_linear_infinite] fill-current"
size={12}
/>
<div slot="text">
The variable was connected through OAuth and the token is not
expired.
</div>
</Popover>
{/if}
</div>
{/if}
</div>
</Cell>
<Cell last shouldStopPropagation>
<Dropdown
items={() => {
let owner = isOwner(path, $userStore, $workspaceStore)
return [
{
displayName: 'Edit',
icon: Pen,
action: () => variableEditor.editVariable(path),
disabled: !canWrite
},
{
displayName: 'Delete',
icon: Trash,
type: 'delete',
action: (event) => {
if (event['shiftKey']) {
deleteVariable(path, account)
} else {
deleteConfirmedCallback = () => {
deleteVariable(path, account)
}
}
},
disabled: !owner
},
...(isDeployable(
is_secret ? 'secret' : 'variable',
path,
deployUiSettings
)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'variable')
}
}
]
: []),
{
displayName: owner ? 'Share' : 'See Permissions',
action: () => {
shareModal.openDrawer(path, 'variable')
},
icon: Share
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: RefreshCw,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadVariables()
}
}
]
: [])
]
}}
/>
</Cell>
</Row>
{/each}
</tbody>
</DataTable>
{/if}
</div>
{:else if tab == 'contextual'}
<div class="overflow-auto">
{#if loading.contextual}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(8) as _}
<Skeleton layout={[[2.8], 0.5]} />
{/each}
{:else}
<PageHeader title="Custom contextual variables" primary={false} />
{#if contextualVariables.filter((x) => x.is_custom).length === 0}
</Tab>
<Tab size="md" value="contextual">
<div class="flex gap-2 items-center my-1">
<DollarSign size={18} />
Contextual
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/variables_and_secrets#contextual-variables"
>
Contextual variables are passed as environment variables when running a script and
depends on the execution context.
</Tooltip>
</div>
</Tab>
</Tabs>
{#if tab == 'workspace'}
<div class="pt-2">
<input placeholder="Search Variable" bind:value={filter} class="input mt-1" />
</div>
<div class="min-h-[56px]">
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
</div>
<div class="relative overflow-x-auto pb-40 pr-4">
{#if !filteredItems}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(3) as _}
<Skeleton layout={[[3.5], 0.5]} />
{/each}
{:else if filteredItems.length == 0}
<div class="flex flex-col items-center justify-center h-full">
<div class="text-md font-medium">No custom contextual variables found</div>
<div class="text-md font-medium">No variables found</div>
<div class="text-sm text-secondary">
Try changing the filters or creating a new variable
</div>
</div>
{:else}
<TableSimple
headers={['Name', 'Value']}
data={contextualVariables.filter((x) => x.is_custom)}
keys={['name', 'value']}
getRowActions={$userStore?.is_admin || $userStore?.is_super_admin
? (row) => {
return [
{
displayName: 'Edit',
action: () => contextualVariableEditor.editVariable(row.name, row.value)
},
{
displayName: 'Delete',
type: 'delete',
action: () => {
deleteContextualVariable(row)
<DataTable size="xs">
<Head>
<tr>
<Cell head first class="!px-0" />
<Cell head>Path</Cell>
<Cell head>Value</Cell>
<Cell head>Description</Cell>
<Cell head />
<Cell head last />
</tr>
</Head>
<tbody class="divide-y">
{#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked }}
<Row>
<Cell class="!px-0 text-center w-12" first>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</Cell>
<Cell>
<a
class="break-all"
id="edit-{path}"
on:click={() => variableEditor.editVariable(path)}
href="#{path}"
>
{#if marked}
{@html marked}
{:else}
{path}
{/if}
</a>
</Cell>
<Cell>
<span class="inline-flex flex-row items-center gap-2">
<div class="text-sm break-words">
{#if value}
{truncate(value, 20)}
{:else}
&lowast;&lowast;&lowast;&lowast;
{/if}
</div>
{#if is_secret}
<Popover notClickable>
<EyeOff size={12} />
<span slot="text">This item is secret</span>
</Popover>
{/if}
</span>
</Cell>
<Cell class="break-words">
<span class="text-xs text-tertiary">{truncate(description ?? '', 50)} </span>
</Cell>
<Cell class="text-center">
<div class="flex flex-row items-center gap-4">
{#if is_linked}
<Popover notClickable>
<Link size={16} />
<div slot="text">
This variable is linked with a resource of the same path. They are
deleted and renamed together.
</div>
</Popover>
{/if}
{#if account}
<Popover notClickable>
<RefreshCw size={16} />
<div slot="text">
This OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
{#if is_refreshed}
<div class="">
{#if refresh_error}
<Popover notClickable>
<div
class="relative inline-flex justify-center items-center w-4 h-4"
>
<Circle
class="text-red-600 animate-ping absolute z-50 w-4 h-4 fill-current"
size={12}
/>
<Circle
class="text-red-600 relative inline-flex fill-current "
size={12}
/>
</div>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover notClickable>
<Circle
class="text-yellow-600 animate-[pulse_5s_linear_infinite] fill-current"
size={12}
/>
<div slot="text">
The access_token is expired, it will get renewed the next time
this variable is fetched or you can request is to be refreshed in
the dropdown on the right.
</div>
</Popover>
{:else}
<Popover notClickable>
<Circle
class="text-green-600 animate-[pulse_5s_linear_infinite] fill-current"
size={12}
/>
<div slot="text">
The variable was connected through OAuth and the token is not
expired.
</div>
</Popover>
{/if}
</div>
{/if}
</div>
</Cell>
<Cell last shouldStopPropagation>
<Dropdown
items={() => {
let owner = isOwner(path, $userStore, $workspaceStore)
return [
{
displayName: 'Edit',
icon: Pen,
action: () => variableEditor.editVariable(path),
disabled: !canWrite
},
{
displayName: 'Delete',
icon: Trash,
type: 'delete',
action: (event) => {
if (event['shiftKey']) {
deleteVariable(path, account)
} else {
deleteConfirmedCallback = () => {
deleteVariable(path, account)
}
}
},
disabled: !owner
},
...(isDeployable(
is_secret ? 'secret' : 'variable',
path,
deployUiSettings
)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'variable')
}
}
]
: []),
{
displayName: owner ? 'Share' : 'See Permissions',
action: () => {
shareModal.openDrawer(path, 'variable')
},
icon: Share
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: RefreshCw,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadVariables()
}
}
]
: [])
]
}}
/>
</Cell>
</Row>
{/each}
</tbody>
</DataTable>
{/if}
</div>
{:else if tab == 'contextual'}
<div class="overflow-auto">
{#if loading.contextual}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(8) as _}
<Skeleton layout={[[2.8], 0.5]} />
{/each}
{:else}
<PageHeader title="Custom contextual variables" primary={false} />
{#if contextualVariables.filter((x) => x.is_custom).length === 0}
<div class="flex flex-col items-center justify-center h-full">
<div class="text-md font-medium">No custom contextual variables found</div>
</div>
{:else}
<TableSimple
headers={['Name', 'Value']}
data={contextualVariables.filter((x) => x.is_custom)}
keys={['name', 'value']}
getRowActions={$userStore?.is_admin || $userStore?.is_super_admin
? (row) => {
return [
{
displayName: 'Edit',
action: () => contextualVariableEditor.editVariable(row.name, row.value)
},
{
displayName: 'Delete',
type: 'delete',
action: () => {
deleteContextualVariable(row)
}
}
}
]
}
: undefined}
]
}
: undefined}
/>
{/if}
<PageHeader title="Contextual variables" primary={false} />
<TableSimple
headers={['Name', 'Example of value', 'Description']}
data={contextualVariables.filter((x) => !x.is_custom)}
keys={['name', 'value', 'description']}
/>
{/if}
<PageHeader title="Contextual variables" primary={false} />
<TableSimple
headers={['Name', 'Example of value', 'Description']}
data={contextualVariables.filter((x) => !x.is_custom)}
keys={['name', 'value', 'description']}
/>
{/if}
</div>
{/if}
</CenteredPage>
</div>
{/if}
</CenteredPage>
</Scroll>
{/if}
<ConfirmationModal