feat(frontend): Rework variable table (#2576)
* feat(frontend): updated variable table * feat(frontend): updated variable table * feat(frontend): fix folder menu * feat(frontend): fix folder menu * fix(frontend): fix icons * fix(frontend): reduce table size
This commit is contained in:
51
frontend/src/lib/components/DropdownV2.svelte
Normal file
51
frontend/src/lib/components/DropdownV2.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { MoreVertical } from 'lucide-svelte'
|
||||
import Menu from './common/menu/MenuV2.svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type Item = {
|
||||
displayName: string
|
||||
action?: (e: CustomEvent<any>) => void
|
||||
icon: any
|
||||
href?: string
|
||||
disabled?: boolean
|
||||
type?: 'action' | 'delete'
|
||||
}
|
||||
|
||||
export let items: Item[] | (() => Item[]) = []
|
||||
|
||||
function computeItems(): Item[] {
|
||||
if (typeof items === 'function') {
|
||||
return items()
|
||||
} else {
|
||||
return items
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu placement="bottom-end" justifyEnd>
|
||||
<div slot="trigger">
|
||||
<MoreVertical size={16} class="w-8 h-8 p-2 hover:bg-surface-hover cursor-pointer rounded-md" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
{#each computeItems() ?? [] as item}
|
||||
<MenuItem
|
||||
on:click={(e) => item?.action?.(e)}
|
||||
href={item?.href}
|
||||
disabled={item?.disabled}
|
||||
class={twMerge(
|
||||
'px-4 py-2 text-primary hover:bg-surface-hover hover:text-primary cursor-pointer text-xs transition-all',
|
||||
'flex flex-row gap-2 items-center',
|
||||
item?.type === 'delete' && 'text-red-500 hover:bg-red-100 hover:text-red-500'
|
||||
)}
|
||||
>
|
||||
{#if item.icon}
|
||||
<svelte:component this={item.icon} size={14} />
|
||||
{/if}
|
||||
{item.displayName}
|
||||
</MenuItem>
|
||||
{/each}
|
||||
</div>
|
||||
</Menu>
|
||||
@@ -3,8 +3,10 @@
|
||||
import Portal from 'svelte-portal'
|
||||
import { offset, flip, shift } from 'svelte-floating-ui/dom'
|
||||
import { createFloatingActions } from 'svelte-floating-ui'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let placement: any = 'bottom-start'
|
||||
export let justifyEnd: boolean = false
|
||||
|
||||
const [floatingRef, floatingContent] = createFloatingActions({
|
||||
strategy: 'fixed',
|
||||
@@ -15,7 +17,7 @@
|
||||
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
|
||||
<div use:floatingRef class="w-full">
|
||||
<MenuButton class="w-full">
|
||||
<MenuButton class={twMerge('w-full', justifyEnd ? 'flex justify-end' : '')}>
|
||||
<slot name="trigger" />
|
||||
</MenuButton>
|
||||
</div>
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
numeric ? 'text-right' : '',
|
||||
head ? 'font-semibold ' : '',
|
||||
$$restProps.class,
|
||||
sticky ? `!p-0 sticky ${first ? 'left-0' : 'right-0'}` : 'px-2 py-3.5',
|
||||
size === 'sm' ? 'px-1.5 py-2.5' : '',
|
||||
size === 'lg' ? 'px-3 py-4' : '',
|
||||
size === 'xs' ? 'px-1 py-1.5' : '',
|
||||
sticky ? `!p-0 sticky ${first ? 'left-0' : 'right-0'}` : 'px-2 py-3.5'
|
||||
size === 'xs' ? 'px-1 py-1.5' : ''
|
||||
)}
|
||||
>
|
||||
{#if sticky}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { FolderService } from '$lib/gen'
|
||||
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import FolderEditor from '$lib/components/FolderEditor.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { faEdit, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button, Drawer, DrawerContent, Popup, Skeleton } from '$lib/components/common'
|
||||
import FolderInfo from '$lib/components/FolderInfo.svelte'
|
||||
import FolderUsageInfo from '$lib/components/FolderUsageInfo.svelte'
|
||||
@@ -16,6 +16,7 @@
|
||||
import Head from '$lib/components/table/Head.svelte'
|
||||
import Cell from '$lib/components/table/Cell.svelte'
|
||||
import Row from '$lib/components/table/Row.svelte'
|
||||
import { Pen, Trash } from 'lucide-svelte'
|
||||
|
||||
type FolderW = Folder & { canWrite: boolean }
|
||||
|
||||
@@ -159,11 +160,10 @@
|
||||
<Cell><FolderInfo members={computeMembers(owners, extra_perms)} /></Cell>
|
||||
<Cell shouldStopPropagation>
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={[
|
||||
items={[
|
||||
{
|
||||
displayName: 'Manage folder',
|
||||
icon: faEdit,
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
editFolderName = name
|
||||
@@ -172,8 +172,7 @@
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
|
||||
icon: faTrash,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
disabled: !canWrite,
|
||||
action: async () => {
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
import { Alert, Badge, Button, Skeleton, Tab, Tabs } from '$lib/components/common'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import ListFilters from '$lib/components/home/ListFilters.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import SearchItems from '$lib/components/SearchItems.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import Cell from '$lib/components/table/Cell.svelte'
|
||||
import DataTable from '$lib/components/table/DataTable.svelte'
|
||||
import Head from '$lib/components/table/Head.svelte'
|
||||
import Row from '$lib/components/table/Row.svelte'
|
||||
import TableSimple from '$lib/components/TableSimple.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import VariableEditor from '$lib/components/VariableEditor.svelte'
|
||||
@@ -19,18 +22,8 @@
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { canWrite, isOwner, truncate } from '$lib/utils'
|
||||
import {
|
||||
faChain,
|
||||
faCircle,
|
||||
faEdit,
|
||||
faEyeSlash,
|
||||
faFileExport,
|
||||
faPlus,
|
||||
faRefresh,
|
||||
faShare,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { Building, DollarSign } from 'lucide-svelte'
|
||||
import { faCircle, faEyeSlash, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Building, DollarSign, FileUp, Link, Pen, RefreshCw, Share, Trash } from 'lucide-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
type ListableVariableW = ListableVariable & { canWrite: boolean }
|
||||
@@ -140,12 +133,13 @@
|
||||
<Tab size="md" value="contextual">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<DollarSign size={18} />
|
||||
Contextual <Tooltip
|
||||
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
|
||||
>
|
||||
on the execution context.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -153,41 +147,55 @@
|
||||
<div class="pt-2">
|
||||
<input placeholder="Search Variable" bind:value={filter} class="input mt-1" />
|
||||
</div>
|
||||
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
|
||||
<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>
|
||||
</div>
|
||||
{:else}
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th class="!px-0" />
|
||||
<th>Path</th>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
<th />
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
<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 }}
|
||||
<tr>
|
||||
<td class="!px-0 text-center">
|
||||
<Row>
|
||||
<Cell class="!px-0 text-center w-12" first>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</td>
|
||||
<td>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
class="break-all"
|
||||
id="edit-{path}"
|
||||
on:click={() => variableEditor.editVariable(path)}
|
||||
href="#{path}"
|
||||
>
|
||||
{#if marked}{@html marked}{:else}{path}{/if}
|
||||
{#if marked}
|
||||
{@html marked}
|
||||
{:else}
|
||||
{path}
|
||||
{/if}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<span class="inline-flex flex-row">
|
||||
<span class="text-sm break-words">
|
||||
{truncate(value ?? '****', 20)}
|
||||
@@ -204,43 +212,39 @@
|
||||
</Popover>
|
||||
{/if}
|
||||
</span>
|
||||
</td>
|
||||
<td class="break-words"
|
||||
><span class="text-xs text-tertiary">{truncate(description ?? '', 50)}</span></td
|
||||
>
|
||||
</Cell>
|
||||
<Cell class="break-words">
|
||||
<span class="text-xs text-tertiary">{truncate(description ?? '', 50)} </span>
|
||||
</Cell>
|
||||
|
||||
<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>
|
||||
<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="w-10">
|
||||
<div class="">
|
||||
{#if refresh_error}
|
||||
<Popover notClickable>
|
||||
<span class="flex h-4 w-4">
|
||||
<div class="relative inline-flex justify-center items-center w-4 h-4">
|
||||
<Icon
|
||||
class="text-red-600 animate-ping absolute inline-flex "
|
||||
class="text-red-600 animate-ping absolute z-50 w-4 h-4"
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during exchange of the refresh token"
|
||||
@@ -251,7 +255,8 @@
|
||||
scale={0.7}
|
||||
label="Error during exchange of the refresh token"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div slot="text">
|
||||
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
|
||||
</div>
|
||||
@@ -286,25 +291,24 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
</Cell>
|
||||
<Cell last shouldStopPropagation>
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={() => {
|
||||
items={() => {
|
||||
let owner = isOwner(path, $userStore, $workspaceStore)
|
||||
return [
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: faEdit,
|
||||
icon: Pen,
|
||||
action: () => variableEditor.editVariable(path),
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Delete',
|
||||
icon: faTrash,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
action: (event) => {
|
||||
if (event?.shiftKey) {
|
||||
if (event['shiftKey']) {
|
||||
deleteVariable(path, account)
|
||||
} else {
|
||||
deleteConfirmedCallback = () => {
|
||||
@@ -316,7 +320,7 @@
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to prod/staging',
|
||||
icon: faFileExport,
|
||||
icon: FileUp,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'variable')
|
||||
}
|
||||
@@ -326,13 +330,13 @@
|
||||
action: () => {
|
||||
shareModal.openDrawer(path, 'variable')
|
||||
},
|
||||
icon: faShare
|
||||
icon: Share
|
||||
},
|
||||
...(account != undefined
|
||||
? [
|
||||
{
|
||||
displayName: 'Refresh token',
|
||||
icon: faRefresh,
|
||||
icon: RefreshCw,
|
||||
action: async () => {
|
||||
await OauthService.refreshToken({
|
||||
workspace: $workspaceStore ?? '',
|
||||
@@ -350,11 +354,11 @@
|
||||
]
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</DataTable>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if tab == 'contextual'}
|
||||
|
||||
Reference in New Issue
Block a user