mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 00:02:13 +00:00
feat(datatables): switch a data table's role from its row badge
The badge is now a button with a chevron, opening the select dropdown over the roles the caller may use — the dropdown alone, not Select's own input. Picking one reconnects the manager as that role, switching data table first when the badge belongs to another one.
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
import { capitalize, onlyAlphaNumAndUnderscore, pluralize } from '$lib/utils'
|
||||
import type { DbFeatures } from './apps/components/display/dbtable/dbFeatures'
|
||||
import Star from './Star.svelte'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import DatatableRoleBadge from './DatatableRoleBadge.svelte'
|
||||
import type { Asset, DataTableTables } from '$lib/gen'
|
||||
import { ADMIN_DATATABLE_ROLE, type DatatableRowAction } from './dbTypes'
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
@@ -79,6 +79,8 @@
|
||||
onSelectDatatable?: (datatable: string) => void
|
||||
/** Role the manager is connected as, when it is not the default one. */
|
||||
currentRole?: string
|
||||
/** Switch a data table's role from its row badge. */
|
||||
onSelectRole?: (datatable: string, role: string) => void
|
||||
pendingAction?: PendingRowAction | undefined
|
||||
/** Row-menu actions on a data table, run against that row's data table. */
|
||||
onDatatableAction?: (datatable: string, action: DatatableRowAction) => void
|
||||
@@ -110,6 +112,7 @@
|
||||
datatableTreeLoading,
|
||||
onSelectDatatable,
|
||||
currentRole,
|
||||
onSelectRole,
|
||||
pendingAction = $bindable(undefined),
|
||||
onDatatableAction,
|
||||
canManageDatatable = false,
|
||||
@@ -211,16 +214,18 @@
|
||||
return datatableTree?.find((d) => d.datatable_name === datatable)?.error
|
||||
}
|
||||
|
||||
/** The role a data table row is reached through, shown as a badge. Left out
|
||||
* where naming it says nothing: a data table without permissions, or one whose
|
||||
* single usable role is already the implicit `admin`. */
|
||||
function roleOf(datatable: string): string | undefined {
|
||||
/** The role a data table row is reached through, and what it can be switched
|
||||
* to. Absent where naming the role says nothing: a data table without
|
||||
* permissions, or one whose single usable role is already the implicit
|
||||
* `admin`. */
|
||||
function roleOf(datatable: string): { role: string; roles: string[] } | undefined {
|
||||
const entry = datatableTree?.find((d) => d.datatable_name === datatable)
|
||||
const usable = entry?.usable_roles ?? []
|
||||
if (usable.length === 0 || (usable.length === 1 && usable[0] === ADMIN_DATATABLE_ROLE)) {
|
||||
const roles = entry?.usable_roles ?? []
|
||||
if (roles.length === 0 || (roles.length === 1 && roles[0] === ADMIN_DATATABLE_ROLE)) {
|
||||
return undefined
|
||||
}
|
||||
return (datatable === currentDatatable ? currentRole : undefined) ?? entry?.default_role
|
||||
const role = (datatable === currentDatatable ? currentRole : undefined) ?? entry?.default_role
|
||||
return role ? { role, roles } : undefined
|
||||
}
|
||||
|
||||
const matchesSearch = (t: string) => t.toLowerCase().includes(search.trim().toLowerCase())
|
||||
@@ -504,6 +509,7 @@
|
||||
{@const dtOpen = isExpanded(root.datatable)}
|
||||
{#if root.datatable !== undefined}
|
||||
{@const hasMenu = !multiSelectMode && onDatatableAction !== undefined}
|
||||
{@const roleInfo = roleOf(root.datatable)}
|
||||
<button
|
||||
class="group w-full text-xs font-normal text-primary flex gap-2 items-center h-8 cursor-pointer pl-3 pr-1 hover:bg-gray-500/10"
|
||||
onclick={() => toggle(root.datatable)}
|
||||
@@ -557,8 +563,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
<span class="truncate text-ellipsis text-left text-xs">{root.datatable}</span>
|
||||
{#if roleOf(root.datatable) !== undefined}
|
||||
<Badge small color="gray">{roleOf(root.datatable)}</Badge>
|
||||
{#if roleInfo}
|
||||
{@const dt = root.datatable}
|
||||
<DatatableRoleBadge
|
||||
role={roleInfo.role}
|
||||
roles={roleInfo.roles}
|
||||
onSelect={(role) => onSelectRole?.(dt, role)}
|
||||
/>
|
||||
{/if}
|
||||
<div class="grow"></div>
|
||||
<div class="shrink-0 w-6 h-8 flex items-center justify-end mr-2">
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
datatableTree?: DataTableTables[]
|
||||
datatableTreeLoading?: boolean
|
||||
onSelectDatatable?: (datatable: string) => void
|
||||
onSelectRole?: (datatable: string, role: string) => void
|
||||
pendingAction?: PendingRowAction | undefined
|
||||
onDatatableAction?: (datatable: string, action: DatatableRowAction) => void
|
||||
canManageDatatable?: boolean
|
||||
@@ -71,6 +72,7 @@
|
||||
datatableTree,
|
||||
datatableTreeLoading,
|
||||
onSelectDatatable,
|
||||
onSelectRole,
|
||||
pendingAction = $bindable(undefined),
|
||||
onDatatableAction,
|
||||
canManageDatatable,
|
||||
@@ -325,6 +327,7 @@
|
||||
{datatableTree}
|
||||
{datatableTreeLoading}
|
||||
{onSelectDatatable}
|
||||
{onSelectRole}
|
||||
currentRole={input.type === 'database' ? input.role : undefined}
|
||||
bind:pendingAction
|
||||
{onDatatableAction}
|
||||
|
||||
@@ -257,6 +257,11 @@
|
||||
datatableTree={uriState.isDatatableInput ? datatables.current : undefined}
|
||||
datatableTreeLoading={datatables.loading}
|
||||
onSelectDatatable={(dt) => (uriState.selectedDatatable = dt)}
|
||||
onSelectRole={(dt, role) => {
|
||||
// Setting the data table clears the role, so the order matters.
|
||||
uriState.selectedDatatable = dt
|
||||
uriState.selectedRole = role
|
||||
}}
|
||||
bind:pendingAction
|
||||
canManageDatatable={!!($superadmin || $userStore?.is_admin)}
|
||||
onDatatableAction={runDatatableAction}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import { ChevronDown } from 'lucide-svelte'
|
||||
import SelectDropdown from './select/SelectDropdown.svelte'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
|
||||
let {
|
||||
role,
|
||||
roles,
|
||||
onSelect
|
||||
}: {
|
||||
/** The role in effect, shown on the badge. */
|
||||
role: string
|
||||
/** The roles the caller may switch to. */
|
||||
roles: string[]
|
||||
onSelect: (role: string) => void
|
||||
} = $props()
|
||||
|
||||
let open = $state(false)
|
||||
let btnEl: HTMLButtonElement | undefined = $state()
|
||||
const items = $derived(roles.map((r) => ({ label: r, value: r })))
|
||||
|
||||
// The table picker's drawer opens at `disposables + 10000`, which the
|
||||
// dropdown's own z-index would sit under.
|
||||
const dropdownClass = 'z-[20000]'
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<span class="relative flex" use:clickOutside={{ onClickOutside: () => (open = false) }}>
|
||||
<button
|
||||
bind:this={btnEl}
|
||||
type="button"
|
||||
class="flex items-center gap-0.5 rounded-md pl-2 pr-1 py-0.5 text-2xs whitespace-nowrap
|
||||
bg-surface-sunken text-primary transition-[filter,transform]
|
||||
hover:brightness-95 active:brightness-90 active:scale-[0.97]
|
||||
{open ? 'brightness-95' : ''}"
|
||||
onclick={(e) => {
|
||||
// The row underneath folds on click, and picking a role is not that.
|
||||
e.stopPropagation()
|
||||
open = !open
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
<ChevronDown size={11} class="text-secondary transition-transform {open ? 'rotate-180' : ''}" />
|
||||
</button>
|
||||
<SelectDropdown
|
||||
processedItems={items}
|
||||
value={role}
|
||||
{open}
|
||||
listAutoWidth={false}
|
||||
class={dropdownClass}
|
||||
getInputRect={btnEl && (() => btnEl!.getBoundingClientRect())}
|
||||
onSelectValue={(item) => {
|
||||
open = false
|
||||
if (item.value !== role) onSelect(item.value)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
let open = $state(false)
|
||||
let selectedDatatable = $state<string | undefined>(undefined)
|
||||
/** Role the query editor connects as; undefined means the data table's default. */
|
||||
let selectedRole = $state<string | undefined>(undefined)
|
||||
|
||||
// For DB manager
|
||||
let dbManagerContent: DBManagerContent | undefined = $state()
|
||||
@@ -125,6 +127,7 @@
|
||||
type: 'database' as const,
|
||||
resourceType: 'postgresql' as const,
|
||||
resourcePath: `datatable://${selectedDatatable}`,
|
||||
role: selectedRole,
|
||||
specificSchema: selectedSchemaKey,
|
||||
specificTable: selectedTableKey
|
||||
}
|
||||
@@ -169,7 +172,7 @@
|
||||
noPadding
|
||||
>
|
||||
{#if dbInput && opWs}
|
||||
{#key selectedDatatable}
|
||||
{#key `${selectedDatatable}~${selectedRole ?? ''}`}
|
||||
<DBManagerContent
|
||||
bind:this={dbManagerContent}
|
||||
input={dbInput}
|
||||
@@ -183,7 +186,8 @@
|
||||
{disabledTables}
|
||||
datatableTree={datatableTree.current}
|
||||
datatableTreeLoading={datatableTree.loading}
|
||||
onSelectDatatable={(dt) => (selectedDatatable = dt)}
|
||||
onSelectDatatable={(dt) => ((selectedDatatable = dt), (selectedRole = undefined))}
|
||||
onSelectRole={(dt, role) => ((selectedDatatable = dt), (selectedRole = role))}
|
||||
bind:pendingAction
|
||||
/>
|
||||
{/key}
|
||||
|
||||
Reference in New Issue
Block a user