fix(frontend): variables and resources uses tab navigation

This commit is contained in:
Ruben Fiszel
2022-12-03 14:23:44 +01:00
parent a6a0f365b3
commit 3ca5b7c274
6 changed files with 539 additions and 482 deletions
@@ -378,9 +378,8 @@
<input type="text" bind:value={description} />
<h2 class="mt-4">Value</h2>
{#if apiTokenApps[resource_type]}
<div class="mb-1 font-semibold text-gray-700 mt-6">Instructions</div>
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resource_type].instructions as step}
@@ -397,6 +396,7 @@
{/if}
{/if}
<h2 class="mt-4">Value</h2>
<div class="mt-4">
<ApiConnectForm password={key ?? ''} {resource_type} bind:args />
</div>
+9 -1
View File
@@ -1,5 +1,8 @@
<script lang="ts">
import { createPopperActions } from 'svelte-popperjs'
export let notClickable = false
const [popperRef, popperContent] = createPopperActions({
placement: 'auto'
})
@@ -37,7 +40,12 @@
}
</script>
<button use:popperRef on:mouseenter={open} on:mouseleave={close}>
<button
class:cursor-default={notClickable}
use:popperRef
on:mouseenter={open}
on:mouseleave={close}
>
<slot />
</button>
{#if showTooltip}
@@ -5,7 +5,7 @@
import type { Schema } from '$lib/common'
import Path from './Path.svelte'
import Required from './Required.svelte'
import { Alert, Button, Drawer } from './common'
import { Alert, Button, Drawer, Skeleton } from './common'
import { userStore, workspaceStore } from '$lib/stores'
import DrawerContent from './common/drawer/DrawerContent.svelte'
@@ -23,10 +23,10 @@
let description: string = ''
let DESCRIPTION_PLACEHOLDER = `You can use markdown to style your description`
let selectedResourceType: string | undefined
let resourceType: ResourceType
let resourceSchema: Schema | undefined
let args: Record<string, any> = {}
let can_write = true
let loadingSchema = false
let error: string | undefined
@@ -39,15 +39,18 @@
export async function initEdit(p: string): Promise<void> {
initialPath = p
path = p
resourceToEdit = undefined
resourceSchema = undefined
loadingSchema = true
drawer.openDrawer?.()
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
description = resourceToEdit!.description ?? ''
selectedResourceType = resourceToEdit!.resource_type
loadResourceType()
args = resourceToEdit!.value
can_write =
resourceToEdit.workspace_id == $workspaceStore &&
canWrite(p, resourceToEdit.extra_perms ?? {}, $userStore)
await loadResourceType()
drawer.openDrawer?.()
}
async function editResource(): Promise<void> {
@@ -80,7 +83,7 @@
async function loadResourceType(): Promise<void> {
if (selectedResourceType) {
try {
resourceType = await ResourceService.getResourceType({
const resourceType = await ResourceService.getResourceType({
workspace: $workspaceStore!,
path: selectedResourceType
})
@@ -90,11 +93,13 @@
}
} catch (err) {
resourceSchema = undefined
loadingSchema = false
rawCode = JSON.stringify(args, null, 2)
}
} else {
sendUserToast(`ResourceType cannot be undefined.`, true)
}
loadingSchema = false
}
let isValid = true
@@ -137,7 +142,9 @@
<h3 class="mt-4">Value</h3>
<div class="text-sm">
{#if resourceSchema && resourceSchema?.properties}
{#if loadingSchema}
<Skeleton layout={[[4]]} />
{:else if resourceSchema && resourceSchema?.properties}
<SchemaForm
disabled={!can_write}
compact
@@ -11,7 +11,7 @@
const dispatch = createEventDispatcher()
</script>
<div class="mt-8 flex flex-col {$$props.class}">
<div class="mt-2 flex flex-col {$$props.class}">
<div class="inline-block min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-300 table-auto">
<thead>
+282 -266
View File
@@ -37,13 +37,15 @@
import AppConnect from '$lib/components/AppConnect.svelte'
import { page } from '$app/stores'
import { onMount } from 'svelte'
import { Button, Alert, Badge, Skeleton } from '$lib/components/common'
import { Button, Alert, Badge, Skeleton, Tab } from '$lib/components/common'
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Popover from '$lib/components/Popover.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import { Building } from 'svelte-lucide'
type ResourceW = ListableResource & { canWrite: boolean }
type ResourceTypeW = ResourceType & { canWrite: boolean }
@@ -168,6 +170,7 @@
})
let disableCustomPrefix = false
let tab: 'workspace' | 'types' = 'workspace'
</script>
<Drawer bind:this={resourceTypeViewer} size="800px">
@@ -234,286 +237,299 @@
tooltip="Save and permission rich objects (JSON) including credentials obtained through OAuth."
>
<div class="flex flex-row space-x-4">
<Button variant="border" size="md" startIcon={{ icon: faPlus }} on:click={startNewType}
>Add a resource type</Button
>
<Button size="md" startIcon={{ icon: faChain }} on:click={() => appConnect.open?.()}>
Add a resource/API
</Button>
</div>
</PageHeader>
<div class="overflow-x-auto pb-40">
{#if loading.resources}
<Tabs bind:selected={tab}>
<Tab size="md" value="workspace">
<div class="flex gap-2 items-center my-1">
<Building size="18px" />
Workspace
</div>
</Tab>
<Tab size="md" value="types">
<div class="flex gap-2 items-center my-1">
Resource Types <Tooltip
>Every resources have Resource Types attached to them which contains its schema and make
it easy in scripts and flows to accept only resources of a specific resource type</Tooltip
>
</div>
</Tab>
</Tabs>
{#if tab == 'workspace'}
<div class="overflow-x-auto pb-40">
{#if loading.resources}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.7]} />
{/each}
{:else}
<TableCustom>
<tr slot="header-row">
<th>path</th>
<th />
<th>resource type</th>
<th>description</th>
<th />
<th />
</tr>
<tbody slot="body">
{#if resources}
{#each resources as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired }}
<tr>
<td>
<a
class="break-words"
href="#{path}"
on:click={() => resourceEditor?.initEdit?.(path)}>{path}</a
>
</td>
<td><SharedBadge {canWrite} extraPerms={extra_perms} /></td>
<td class="px-2"
><a
href="#{name}"
on:click={() => {
const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type)
if (linkedRt) {
resourceTypeViewerObj = {
rt: linkedRt.name,
schema: linkedRt.schema,
description: linkedRt.description ?? ''
}
resourceTypeViewer.openDrawer?.()
} else {
sendUserToast(
`Resource type ${resource_type} not found in workspace.`,
true
)
}
}}><IconedResourceType name={resource_type} after={true} /></a
></td
>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 30)} /></span
></td
>
<td class="text-center">
<div class="flex flex-row">
<div class="w-10">
{#if is_linked}
<Popover>
<Icon data={faChain} />
<div slot="text">
This resource is linked with a variable of the same path. They are
deleted and renamed together.
</div>
</Popover>
{/if}
</div>
<div class="w-10">
{#if account}
<Popover>
<Icon data={faRefresh} />
<div slot="text">
The OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
</div>
{#if is_oauth}
<div class="w-10">
{#if refresh_error}
<Popover>
<Icon
class="text-red-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Error during exchange of the refresh token"
/>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover>
<Icon
class="text-yellow-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is expired"
/>
<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>
<Icon
class="text-green-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is tied to an OAuth app"
/>
<div slot="text">
The resource was connected through OAuth and the token is not
expired.
</div>
</Popover>
{/if}
</div>
{/if}
</div>
</td>
<td>
<Dropdown
dropdownItems={[
{
displayName: 'Share',
icon: faShare,
disabled: !canWrite,
action: () => {
shareModal.openDrawer?.(path)
}
},
{
displayName: 'Edit',
icon: faEdit,
disabled: !canWrite,
action: () => {
resourceEditor?.initEdit?.(path)
}
},
{
displayName: 'Delete',
disabled: !canWrite,
icon: faTrash,
type: 'delete',
action: (event) => {
if (event?.shiftKey) {
deleteResource(path, account)
} else {
deleteConfirmedCallback = () => {
deleteResource(path, account)
}
}
}
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: faRefresh,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadResources()
}
}
]
: [])
]}
relative={true}
/>
</td>
</tr>
{/each}
{:else if resources}
<tr> No resources to display</tr>
{:else}
<tr>Loading...</tr>
{/if}
</tbody>
</TableCustom>
{/if}
</div>
{:else if tab == 'types'}
{#if loading.types}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.7]} />
{/each}
{:else}
<TableCustom>
<tr slot="header-row">
<th>path</th>
<th />
<th>resource type</th>
<th>description</th>
<th />
<th />
</tr>
<tbody slot="body">
{#if resources}
{#each resources as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired }}
<tr>
<td>
<a
class="break-words"
href="#{path}"
on:click={() => resourceEditor?.initEdit?.(path)}>{path}</a
>
</td>
<td><SharedBadge {canWrite} extraPerms={extra_perms} /></td>
<td class="px-2"
><a
href="#{name}"
on:click={() => {
const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type)
if (linkedRt) {
<div class="overflow-auto">
<TableCustom>
<tr slot="header-row">
<th>name</th>
<th>description</th>
<th />
</tr>
<tbody slot="body">
{#if resourceTypes}
{#each resourceTypes as { name, description, schema, canWrite }}
<tr>
<td
><a
href="#{name}"
on:click={() => {
resourceTypeViewerObj = {
rt: linkedRt.name,
schema: linkedRt.schema,
description: linkedRt.description ?? ''
rt: name,
schema: schema,
description: description ?? ''
}
resourceTypeViewer.openDrawer?.()
} else {
sendUserToast(
`Resource type ${resource_type} not found in workspace.`,
true
)
}
}}><IconedResourceType name={resource_type} after={true} /></a
></td
>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 30)} /></span
></td
>
<td class="text-center">
<div class="flex flex-row">
<div class="w-10">
{#if is_linked}
<Popover>
<Icon data={faChain} />
<div slot="text">
This resource is linked with a variable of the same path. They are
deleted and renamed together.
</div>
</Popover>
{/if}
</div>
<div class="w-10">
{#if account}
<Popover>
<Icon data={faRefresh} />
<div slot="text">
The OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
</div>
{#if is_oauth}
<div class="w-10">
{#if refresh_error}
<Popover>
<Icon
class="text-red-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Error during exchange of the refresh token"
/>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover>
<Icon
class="text-yellow-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is expired"
/>
<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>
<Icon
class="text-green-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is tied to an OAuth app"
/>
<div slot="text">
The resource was connected through OAuth and the token is not expired.
</div>
</Popover>
{/if}
</div>
}}><IconedResourceType after={true} {name} /></a
></td
>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 200)} /></span
></td
>
<td>
{#if !canWrite}
<Badge
>Shared globally<Tooltip
>This resource type is from the 'starter' workspace shared with all
workspaces</Tooltip
></Badge
>
{:else if $userStore?.is_admin}
<Button
size="sm"
color="red"
variant="border"
startIcon={{ icon: faTrash }}
on:click={() => handleDeleteResourceType(name)}
>
Delete
</Button>
{:else}
<Badge
>Non Editable <Tooltip
>Since resource types are shared with the whole workspace, only admins can
edit/delete them</Tooltip
></Badge
>
{/if}
</div>
</td>
<td>
<Dropdown
dropdownItems={[
{
displayName: 'Share',
icon: faShare,
disabled: !canWrite,
action: () => {
shareModal.openDrawer?.(path)
}
},
{
displayName: 'Edit',
icon: faEdit,
disabled: !canWrite,
action: () => {
resourceEditor?.initEdit?.(path)
}
},
{
displayName: 'Delete',
disabled: !canWrite,
icon: faTrash,
type: 'delete',
action: (event) => {
if (event?.shiftKey) {
deleteResource(path, account)
} else {
deleteConfirmedCallback = () => {
deleteResource(path, account)
}
}
}
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: faRefresh,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadResources()
}
}
]
: [])
]}
relative={true}
/>
</td>
</tr>
{/each}
{:else if resources}
<tr> No resources to display</tr>
{:else}
<tr>Loading...</tr>
{/if}
</tbody>
</TableCustom>
</td>
</tr>
{/each}
{:else if resources}
<tr> No resources types to display</tr>
{:else}
<tr>Loading...</tr>
{/if}
</tbody>
</TableCustom>
</div>
{/if}
</div>
<PageHeader
title="Resources types"
primary={false}
tooltip="Schema and label to filter resources by type"
>
<Button size="md" startIcon={{ icon: faPlus }} on:click={startNewType}
>Add a resource type</Button
>
</PageHeader>
{#if loading.types}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.7]} />
{/each}
{:else}
<div class="overflow-auto">
<TableCustom>
<tr slot="header-row">
<th>name</th>
<th>description</th>
<th />
</tr>
<tbody slot="body">
{#if resourceTypes}
{#each resourceTypes as { name, description, schema, canWrite }}
<tr>
<td
><a
href="#{name}"
on:click={() => {
resourceTypeViewerObj = {
rt: name,
schema: schema,
description: description ?? ''
}
resourceTypeViewer.openDrawer?.()
}}><IconedResourceType after={true} {name} /></a
></td
>
<td
><span class="text-gray-500 text-xs"
><SvelteMarkdown source={truncate(description ?? '', 200)} /></span
></td
>
<td>
{#if !canWrite}
<Badge
>Shared globally<Tooltip
>This resource type is from the 'starter' workspace shared with all
workspaces</Tooltip
></Badge
>
{:else if $userStore?.is_admin}
<Button
size="sm"
color="red"
variant="border"
startIcon={{ icon: faTrash }}
on:click={() => handleDeleteResourceType(name)}
>
Delete
</Button>
{:else}
<Badge
>Non Editable <Tooltip
>Since resource types are shared with the whole workspace, only admins can
edit/delete them</Tooltip
></Badge
>
{/if}
</td>
</tr>
{/each}
{:else if resources}
<tr> No resources types to display</tr>
{:else}
<tr>Loading...</tr>
{/if}
</tbody>
</TableCustom>
</div>
{/if}
</CenteredPage>
+232 -206
View File
@@ -30,14 +30,19 @@
faEdit,
faShare
} from '@fortawesome/free-solid-svg-icons'
import { Button } from '$lib/components/common'
import { Button, Tab, Tabs } from '$lib/components/common'
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import { Alert, Badge, Skeleton } from '$lib/components/common'
import Popover from '$lib/components/Popover.svelte'
import { Building, DollarSign } from 'svelte-lucide'
import Tooltip from '$lib/components/Tooltip.svelte'
import SearchItems from '$lib/components/SearchItems.svelte'
type ListableVariableW = ListableVariable & { canWrite: boolean }
let filter = ''
let variables: ListableVariableW[] = []
let filteredItems: ListableVariableW[] = []
let contextualVariables: ContextualVariable[] = []
let shareModal: ShareModal
let variableEditor: VariableEditor
@@ -81,8 +86,16 @@
loadContextualVariables()
}
}
let tab: 'workspace' | 'contextual' = 'workspace'
</script>
<SearchItems
{filter}
items={variables}
bind:filteredItems
f={(x) => x.path + ' ' + x.description}
/>
<CenteredPage>
<PageHeader
title="Variables"
@@ -94,188 +107,6 @@
</PageHeader>
<VariableEditor bind:this={variableEditor} on:create={loadVariables} />
<div class="relative overflow-x-auto pb-40 pr-4">
{#if loading.variables}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(3) as _}
<Skeleton layout={[[3.5], 0.5]} />
{/each}
{:else}
<TableCustom>
<tr slot="header-row">
<th>path</th>
<th>value</th>
<th>description</th>
<th />
<th />
</tr>
<tbody slot="body">
{#each variables as { path, value, is_secret, description, extra_perms, canWrite, account, is_oauth, is_expired, refresh_error, is_linked }}
<tr>
<td
><a
class="break-words"
id="edit-{path}"
on:click={() => variableEditor.editVariable(path)}
href="#{path}">{path}</a
>
<div><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td>
<span class="inline-flex flex-row">
<span class="text-sm break-words">
{truncate(value ?? '****', 20)}
</span>
{#if is_secret}
<Popover>
<Icon
label="Secret"
class="text-gray-700 mb-2 ml-2"
data={faEyeSlash}
scale={0.8}
/>
<span slot="text">This item is secret</span>
</Popover>
{/if}
</span>
</td>
<td class="break-words"
><span class="text-xs text-gray-500">{truncate(description ?? '', 50)}</span></td
>
<td class="text-center">
<div class="flex flex-row">
<div class="w-10">
{#if is_linked}
<Popover>
<Icon data={faChain} />
<div slot="text">
This variable is linked with a resource of the same path. They are deleted
and renamed together.
</div>
</Popover>
{/if}
</div>
<div class="w-10">
{#if account}
<Popover>
<Icon data={faRefresh} />
<div slot="text">
This OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
</div>
{#if is_oauth}
<div class="w-10">
{#if refresh_error}
<Popover>
<Icon
class="text-red-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Error during exchange of the refresh token"
/>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover>
<Icon
class="text-yellow-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is expired"
/>
<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>
<Icon
class="text-green-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is tied to an OAuth app"
/>
<div slot="text">
The variable was connected through OAuth and the token is not expired.
</div>
</Popover>
{/if}
</div>
{/if}
</div>
</td>
<td
><Dropdown
dropdownItems={[
{
displayName: 'Edit',
icon: faEdit,
action: () => variableEditor.editVariable(path),
disabled: !canWrite
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
action: (event) => {
if (event?.shiftKey) {
deleteVariable(path, account)
} else {
deleteConfirmedCallback = () => {
deleteVariable(path, account)
}
}
},
disabled: !canWrite
},
{
displayName: 'Share',
action: () => {
shareModal.openDrawer(path)
},
icon: faShare,
disabled: !canWrite
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: faRefresh,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadVariables()
}
}
]
: [])
]}
relative={true}
/></td
>
</tr>
{/each}
</tbody>
</TableCustom>
{/if}
</div>
<ShareModal
bind:this={shareModal}
kind="variable"
@@ -284,30 +115,225 @@
}}
/>
<PageHeader
title="Contextual Variables"
tooltip="
Contextual variables are utility variables passed to your environment when running a script
and depends on the execution context. Variables cannot use a reseved variable name."
primary={false}
/>
<Tabs bind:selected={tab}>
<Tab size="md" value="workspace">
<div class="flex gap-2 items-center my-1">
<Building size="18px" />
Workspace
</div>
</Tab>
<Tab size="md" value="contextual">
<div class="flex gap-2 items-center my-1">
<DollarSign size="18px" />
Contextual <Tooltip>
Contextual variables are utility variables passed to your environment 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="relative overflow-x-auto pb-40 pr-4">
{#if loading.variables}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(3) as _}
<Skeleton layout={[[3.5], 0.5]} />
{/each}
{:else}
<TableCustom>
<tr slot="header-row">
<th>path</th>
<div class="overflow-auto">
<div class="my-5" />
{#if loading.contextual}
<Skeleton layout={[0.5, [2], 1]} />
{#each new Array(8) as _}
<Skeleton layout={[[2.8], 0.5]} />
{/each}
{:else}
<TableSimple
headers={['name', 'example of value', 'description']}
data={contextualVariables}
keys={['name', 'value', 'description']}
twTextSize="text-sm"
/>
{/if}
</div>
<th>value</th>
<th>description</th>
<th />
<th />
</tr>
<tbody slot="body">
{#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_oauth, is_expired, refresh_error, is_linked }}
<tr>
<td
><a
class="break-words"
id="edit-{path}"
on:click={() => variableEditor.editVariable(path)}
href="#{path}">{path}</a
>
<div><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
</td>
<td>
<span class="inline-flex flex-row">
<span class="text-sm break-words">
{truncate(value ?? '****', 20)}
</span>
{#if is_secret}
<Popover notClickable>
<Icon
label="Secret"
class="text-gray-700 mb-2 ml-2"
data={faEyeSlash}
scale={0.8}
/>
<span slot="text">This item is secret</span>
</Popover>
{/if}
</span>
</td>
<td class="break-words"
><span class="text-xs text-gray-500">{truncate(description ?? '', 50)}</span></td
>
<td class="text-center">
<div class="flex flex-row">
<div class="w-10">
{#if is_linked}
<Popover notClickable>
<Icon data={faChain} />
<div slot="text">
This variable is linked with a resource of the same path. They are
deleted and renamed together.
</div>
</Popover>
{/if}
</div>
<div class="w-10">
{#if account}
<Popover notClickable>
<Icon data={faRefresh} />
<div slot="text">
This OAuth token will be kept up-to-date in the background by Windmill
using its refresh token
</div>
</Popover>
{/if}
</div>
{#if is_oauth}
<div class="w-10">
{#if refresh_error}
<Popover notClickable>
<Icon
class="text-red-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Error during exchange of the refresh token"
/>
<div slot="text">
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
</div>
</Popover>
{:else if is_expired}
<Popover notClickable>
<Icon
class="text-yellow-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is expired"
/>
<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>
<Icon
class="text-green-600 animate-[pulse_5s_linear_infinite]"
data={faCircle}
scale={0.7}
label="Variable is tied to an OAuth app"
/>
<div slot="text">
The variable was connected through OAuth and the token is not expired.
</div>
</Popover>
{/if}
</div>
{/if}
</div>
</td>
<td
><Dropdown
dropdownItems={[
{
displayName: 'Edit',
icon: faEdit,
action: () => variableEditor.editVariable(path),
disabled: !canWrite
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
action: (event) => {
if (event?.shiftKey) {
deleteVariable(path, account)
} else {
deleteConfirmedCallback = () => {
deleteVariable(path, account)
}
}
},
disabled: !canWrite
},
{
displayName: 'Share',
action: () => {
shareModal.openDrawer(path)
},
icon: faShare,
disabled: !canWrite
},
...(account != undefined
? [
{
displayName: 'Refresh token',
icon: faRefresh,
action: async () => {
await OauthService.refreshToken({
workspace: $workspaceStore ?? '',
id: account ?? 0,
requestBody: {
path
}
})
sendUserToast('Token refreshed')
loadVariables()
}
}
]
: [])
]}
relative={true}
/></td
>
</tr>
{/each}
</tbody>
</TableCustom>
{/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}
<TableSimple
headers={['name', 'example of value', 'description']}
data={contextualVariables}
keys={['name', 'value', 'description']}
twTextSize="text-sm"
/>
{/if}
</div>
{/if}
</CenteredPage>
<ConfirmationModal