mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 16:03:06 +00:00
use scroll for Resources page
This commit is contained in:
@@ -60,6 +60,7 @@
|
||||
import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
|
||||
import ResourceEditorDrawer from '$lib/components/ResourceEditorDrawer.svelte'
|
||||
import GfmMarkdown from '$lib/components/GfmMarkdown.svelte'
|
||||
import Scroll from '$lib/components/Scroll.svelte'
|
||||
|
||||
type ResourceW = ListableResource & { canWrite: boolean; marked?: string }
|
||||
type ResourceTypeW = ResourceType & { canWrite: boolean }
|
||||
@@ -603,422 +604,429 @@
|
||||
f={(x) => x.path + ' ' + x.resource_type + ' ' + x.description + ' '}
|
||||
/>
|
||||
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find(_ => _.id === $workspaceStore)?.operator_settings?.resources}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Resources"
|
||||
tooltip="Save and permission rich objects (JSON) including credentials obtained through OAuth."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
|
||||
>
|
||||
<div class="flex flex-row justify-end gap-4">
|
||||
<Button variant="border" size="md" startIcon={{ icon: Plus }} on:click={startNewType}>
|
||||
Add resource type
|
||||
</Button>
|
||||
<Button size="md" startIcon={{ icon: Link }} on:click={() => appConnect.open?.()}>
|
||||
Add resource
|
||||
</Button>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div class="flex justify-between">
|
||||
<Tabs
|
||||
class="w-full"
|
||||
bind:selected={tab}
|
||||
on:selected={(e) => {
|
||||
if (e.detail == 'cache') {
|
||||
loading.resources = true
|
||||
loadCache()
|
||||
} else if (e.detail == 'states') {
|
||||
loading.resources = true
|
||||
loadState()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tab size="md" value="workspace">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Workspace
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="types">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Resource Types
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
|
||||
>
|
||||
Every resource has a Resource Type attached to it which contains its schema and make it
|
||||
easy in scripts and flows to accept only resources of a specific resource type.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="states">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
States
|
||||
<Tooltip>
|
||||
States are actually resources (but excluded from the Workspace tab for clarity). States
|
||||
are used by scripts to keep data persistent between runs of the same script by the same
|
||||
trigger (schedule or user)
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="cache">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Cache
|
||||
<Tooltip>
|
||||
Cached results are actually resources (but excluded from the Workspace tab for clarity).
|
||||
Cache are used by flows's step to cache result to avoid recomputing unnecessarily
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="theme">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Theme
|
||||
<Tooltip>
|
||||
Theme are actually resources (but excluded from the Workspace tab for clarity). Theme
|
||||
are used by the apps to customize their look and feel.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div class="flex">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
on:click={reload}
|
||||
startIcon={{
|
||||
icon: RotateCw,
|
||||
classes: loading.resources || loading.types ? 'animate-spin' : ''
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if $userStore?.operator && $workspaceStore && !$userWorkspaces.find((_) => _.id === $workspaceStore)?.operator_settings?.resources}
|
||||
<div class="bg-red-100 border-l-4 border-red-600 text-orange-700 p-4 m-4 mt-12" role="alert">
|
||||
<p class="font-bold">Unauthorized</p>
|
||||
<p>Page not available for operators</p>
|
||||
</div>
|
||||
{#if tab == 'workspace' || tab == 'states' || tab == 'cache' || tab == 'theme'}
|
||||
<div class="pt-2">
|
||||
<input placeholder="Search Resource" bind:value={filter} class="input mt-1" />
|
||||
</div>
|
||||
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
{#if tab != 'states' && tab != 'cache'}
|
||||
<ListFilters
|
||||
queryName="app_filter"
|
||||
bind:selectedFilter={typeFilter}
|
||||
filters={types}
|
||||
resourceType
|
||||
/>
|
||||
{:else}
|
||||
<div class="h-4" />
|
||||
{/if}
|
||||
|
||||
<div class="overflow-x-auto pb-40 mt-4">
|
||||
{#if loading.resources}
|
||||
<Skeleton layout={[0.5, [2], 1]} />
|
||||
{#each new Array(6) as _}
|
||||
<Skeleton layout={[[4], 0.7]} />
|
||||
{/each}
|
||||
{:else if filteredItems?.length == 0}
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<div class="text-md font-medium">No resources found</div>
|
||||
<div class="text-sm text-secondary">
|
||||
Try changing the filters or creating a new resource
|
||||
</div>
|
||||
{:else}
|
||||
<Scroll class="h-full w-full">
|
||||
<CenteredPage>
|
||||
<PageHeader
|
||||
title="Resources"
|
||||
tooltip="Save and permission rich objects (JSON) including credentials obtained through OAuth."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
|
||||
>
|
||||
<div class="flex flex-row justify-end gap-4">
|
||||
<Button variant="border" size="md" startIcon={{ icon: Plus }} on:click={startNewType}>
|
||||
Add resource type
|
||||
</Button>
|
||||
<Button size="md" startIcon={{ icon: Link }} on:click={() => appConnect.open?.()}>
|
||||
Add resource
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<DataTable>
|
||||
<Head>
|
||||
<Row>
|
||||
<Cell head first />
|
||||
<Cell head>Path</Cell>
|
||||
<Cell head>Resource type</Cell>
|
||||
<Cell head>Description</Cell>
|
||||
<Cell head />
|
||||
<Cell head last />
|
||||
</Row>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if filteredItems}
|
||||
{#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed }}
|
||||
</PageHeader>
|
||||
<div class="flex justify-between">
|
||||
<Tabs
|
||||
class="w-full"
|
||||
bind:selected={tab}
|
||||
on:selected={(e) => {
|
||||
if (e.detail == 'cache') {
|
||||
loading.resources = true
|
||||
loadCache()
|
||||
} else if (e.detail == 'states') {
|
||||
loading.resources = true
|
||||
loadState()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tab size="md" value="workspace">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Workspace
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="types">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Resource Types
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
|
||||
>
|
||||
Every resource has a Resource Type attached to it which contains its schema and make
|
||||
it easy in scripts and flows to accept only resources of a specific resource type.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="states">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
States
|
||||
<Tooltip>
|
||||
States are actually resources (but excluded from the Workspace tab for clarity).
|
||||
States are used by scripts to keep data persistent between runs of the same script
|
||||
by the same trigger (schedule or user)
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="cache">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Cache
|
||||
<Tooltip>
|
||||
Cached results are actually resources (but excluded from the Workspace tab for
|
||||
clarity). Cache are used by flows's step to cache result to avoid recomputing
|
||||
unnecessarily
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="md" value="theme">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
Theme
|
||||
<Tooltip>
|
||||
Theme are actually resources (but excluded from the Workspace tab for clarity).
|
||||
Theme are used by the apps to customize their look and feel.
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div class="flex">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
on:click={reload}
|
||||
startIcon={{
|
||||
icon: RotateCw,
|
||||
classes: loading.resources || loading.types ? 'animate-spin' : ''
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if tab == 'workspace' || tab == 'states' || tab == 'cache' || tab == 'theme'}
|
||||
<div class="pt-2">
|
||||
<input placeholder="Search Resource" bind:value={filter} class="input mt-1" />
|
||||
</div>
|
||||
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
{#if tab != 'states' && tab != 'cache'}
|
||||
<ListFilters
|
||||
queryName="app_filter"
|
||||
bind:selectedFilter={typeFilter}
|
||||
filters={types}
|
||||
resourceType
|
||||
/>
|
||||
{:else}
|
||||
<div class="h-4" />
|
||||
{/if}
|
||||
|
||||
<div class="overflow-x-auto pb-40 mt-4">
|
||||
{#if loading.resources}
|
||||
<Skeleton layout={[0.5, [2], 1]} />
|
||||
{#each new Array(6) as _}
|
||||
<Skeleton layout={[[4], 0.7]} />
|
||||
{/each}
|
||||
{:else if filteredItems?.length == 0}
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<div class="text-md font-medium">No resources found</div>
|
||||
<div class="text-sm text-secondary">
|
||||
Try changing the filters or creating a new resource
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<DataTable>
|
||||
<Head>
|
||||
<Row>
|
||||
<Cell first>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
class="break-all"
|
||||
href="#{path}"
|
||||
on:click={() => resourceEditor?.initEdit?.(path)}
|
||||
>{#if marked}{@html marked}{:else}{path}{/if}</a
|
||||
>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
href="#{name}"
|
||||
on:click={() => {
|
||||
const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type)
|
||||
if (linkedRt) {
|
||||
resourceTypeViewerObj = {
|
||||
rt: linkedRt.name,
|
||||
//@ts-ignore
|
||||
schema: linkedRt.schema,
|
||||
description: linkedRt.description ?? '',
|
||||
formatExtension: linkedRt.format_extension
|
||||
}
|
||||
resourceTypeViewer.openDrawer?.()
|
||||
} else {
|
||||
sendUserToast(
|
||||
`Resource type ${resource_type} not found in workspace.`,
|
||||
true
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconedResourceType
|
||||
name={resource_type}
|
||||
after={true}
|
||||
formatExtension={resourceNameToFileExt(resource_type)}
|
||||
/>
|
||||
</a>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<span class="text-tertiary text-xs">
|
||||
{removeMarkdown(truncate(description ?? '', 30))}
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex flex-row text-center">
|
||||
<div class="w-10">
|
||||
{#if is_linked}
|
||||
<Popover>
|
||||
<Link />
|
||||
<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 is_refreshed}
|
||||
<Popover>
|
||||
<RotateCw />
|
||||
<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>
|
||||
<Cell head first />
|
||||
<Cell head>Path</Cell>
|
||||
<Cell head>Resource type</Cell>
|
||||
<Cell head>Description</Cell>
|
||||
<Cell head />
|
||||
<Cell head last />
|
||||
</Row>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if filteredItems}
|
||||
{#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed }}
|
||||
<Row>
|
||||
<Cell first>
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
class="break-all"
|
||||
href="#{path}"
|
||||
on:click={() => resourceEditor?.initEdit?.(path)}
|
||||
>{#if marked}{@html marked}{:else}{path}{/if}</a
|
||||
>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<a
|
||||
href="#{name}"
|
||||
on:click={() => {
|
||||
const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type)
|
||||
if (linkedRt) {
|
||||
resourceTypeViewerObj = {
|
||||
rt: linkedRt.name,
|
||||
//@ts-ignore
|
||||
schema: linkedRt.schema,
|
||||
description: linkedRt.description ?? '',
|
||||
formatExtension: linkedRt.format_extension
|
||||
}
|
||||
resourceTypeViewer.openDrawer?.()
|
||||
} else {
|
||||
sendUserToast(
|
||||
`Resource type ${resource_type} not found in workspace.`,
|
||||
true
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconedResourceType
|
||||
name={resource_type}
|
||||
after={true}
|
||||
formatExtension={resourceNameToFileExt(resource_type)}
|
||||
/>
|
||||
</a>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<span class="text-tertiary text-xs">
|
||||
{removeMarkdown(truncate(description ?? '', 30))}
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex flex-row text-center">
|
||||
<div class="w-10">
|
||||
{#if is_linked}
|
||||
<Popover>
|
||||
<Link />
|
||||
<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 is_refreshed}
|
||||
<Popover>
|
||||
<RotateCw />
|
||||
<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 pt-1.5">
|
||||
{#if refresh_error}
|
||||
<Popover>
|
||||
<Circle
|
||||
class="text-red-600 animate-[pulse_5s_linear_infinite] fill-current"
|
||||
size={12}
|
||||
/>
|
||||
<div slot="text">
|
||||
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
|
||||
</div>
|
||||
</Popover>
|
||||
{:else if is_expired}
|
||||
<Popover>
|
||||
<Circle
|
||||
class="text-yellow-600 animate-[pulse_5s_linear_infinite] fill-current"
|
||||
size={12}
|
||||
/>
|
||||
{#if is_oauth}
|
||||
<div class="w-10 pt-1.5">
|
||||
{#if refresh_error}
|
||||
<Popover>
|
||||
<Circle
|
||||
class="text-red-600 animate-[pulse_5s_linear_infinite] fill-current"
|
||||
size={12}
|
||||
/>
|
||||
<div slot="text">
|
||||
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
|
||||
</div>
|
||||
</Popover>
|
||||
{:else if is_expired}
|
||||
<Popover>
|
||||
<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>
|
||||
<Circle
|
||||
class="text-green-600 animate-[pulse_5s_linear_infinite] fill-current"
|
||||
size={12}
|
||||
/>
|
||||
<div slot="text">
|
||||
The resource was connected through OAuth and the token is not
|
||||
expired.
|
||||
</div>
|
||||
</Popover>
|
||||
<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>
|
||||
<Circle
|
||||
class="text-green-600 animate-[pulse_5s_linear_infinite] fill-current"
|
||||
size={12}
|
||||
/>
|
||||
<div slot="text">
|
||||
The resource was connected through OAuth and the token is not
|
||||
expired.
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: !canWrite ? 'View Permissions' : 'Share',
|
||||
icon: Share,
|
||||
action: () => {
|
||||
shareModal.openDrawer?.(path, 'resource')
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
resourceEditor?.initEdit?.(path)
|
||||
}
|
||||
},
|
||||
...(isDeployable('resource', path, deployUiSettings)
|
||||
? [
|
||||
{
|
||||
displayName: 'Deploy to prod/staging',
|
||||
icon: FileUp,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'resource')
|
||||
}
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
displayName: !canWrite ? 'View Permissions' : 'Share',
|
||||
icon: Share,
|
||||
action: () => {
|
||||
shareModal.openDrawer?.(path, 'resource')
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
displayName: 'Delete',
|
||||
disabled: !canWrite,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
action: (event) => {
|
||||
// TODO
|
||||
// @ts-ignore
|
||||
if (event?.shiftKey) {
|
||||
deleteResource(path, account)
|
||||
} else {
|
||||
deleteConfirmedCallback = () => {
|
||||
deleteResource(path, account)
|
||||
},
|
||||
{
|
||||
displayName: 'Edit',
|
||||
icon: Pen,
|
||||
disabled: !canWrite,
|
||||
action: () => {
|
||||
resourceEditor?.initEdit?.(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
...(account != undefined
|
||||
? [
|
||||
{
|
||||
displayName: 'Refresh token',
|
||||
icon: RotateCw,
|
||||
action: async () => {
|
||||
await OauthService.refreshToken({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: account ?? 0,
|
||||
requestBody: {
|
||||
path
|
||||
},
|
||||
...(isDeployable('resource', path, deployUiSettings)
|
||||
? [
|
||||
{
|
||||
displayName: 'Deploy to prod/staging',
|
||||
icon: FileUp,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'resource')
|
||||
}
|
||||
})
|
||||
sendUserToast('Token refreshed')
|
||||
loadResources()
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
displayName: 'Delete',
|
||||
disabled: !canWrite,
|
||||
icon: Trash,
|
||||
type: 'delete',
|
||||
action: (event) => {
|
||||
// TODO
|
||||
// @ts-ignore
|
||||
if (event?.shiftKey) {
|
||||
deleteResource(path, account)
|
||||
} else {
|
||||
deleteConfirmedCallback = () => {
|
||||
deleteResource(path, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{/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}
|
||||
<div class="overflow-auto mt-4">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<Row>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head>Description</Cell>
|
||||
<Cell head last />
|
||||
</Row>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if resourceTypes}
|
||||
{#each resourceTypes as { name, description, schema, canWrite, format_extension }}
|
||||
},
|
||||
...(account != undefined
|
||||
? [
|
||||
{
|
||||
displayName: 'Refresh token',
|
||||
icon: RotateCw,
|
||||
action: async () => {
|
||||
await OauthService.refreshToken({
|
||||
workspace: $workspaceStore ?? '',
|
||||
id: account ?? 0,
|
||||
requestBody: {
|
||||
path
|
||||
}
|
||||
})
|
||||
sendUserToast('Token refreshed')
|
||||
loadResources()
|
||||
}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
/>
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
{/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}
|
||||
<div class="overflow-auto mt-4">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<Row>
|
||||
<Cell first>
|
||||
<a
|
||||
href="#{name}"
|
||||
on:click={() => {
|
||||
resourceTypeViewerObj = {
|
||||
rt: name,
|
||||
//@ts-ignore
|
||||
schema: schema,
|
||||
description: description ?? '',
|
||||
formatExtension: format_extension
|
||||
}
|
||||
|
||||
resourceTypeViewer.openDrawer?.()
|
||||
}}
|
||||
>
|
||||
<IconedResourceType after={true} {name} formatExtension={format_extension} />
|
||||
</a>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<span class="text-tertiary text-xs w-96 flex flex-wrap whitespace-pre-wrap">
|
||||
{removeMarkdown(truncate(description ?? '', 200))}
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
{#if !canWrite}
|
||||
<Badge>
|
||||
Shared globally
|
||||
<Tooltip>
|
||||
This resource type is from the 'admins' workspace shared with all
|
||||
workspaces
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
<div class="flex flex-row-reverse gap-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
btnClasses="border-0"
|
||||
startIcon={{ icon: Trash }}
|
||||
on:click={() => handleDeleteResourceType(name)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
startIcon={{ icon: Pen }}
|
||||
on:click={() => startEditResourceType(name)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Badge>
|
||||
Non Editable
|
||||
<Tooltip>
|
||||
Since resource types are shared with the whole workspace, only admins can
|
||||
edit/delete them
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
{/if}
|
||||
</Cell>
|
||||
<Cell head first>Name</Cell>
|
||||
<Cell head>Description</Cell>
|
||||
<Cell head last />
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
</Head>
|
||||
<tbody class="divide-y bg-surface">
|
||||
{#if resourceTypes}
|
||||
{#each resourceTypes as { name, description, schema, canWrite, format_extension }}
|
||||
<Row>
|
||||
<Cell first>
|
||||
<a
|
||||
href="#{name}"
|
||||
on:click={() => {
|
||||
resourceTypeViewerObj = {
|
||||
rt: name,
|
||||
//@ts-ignore
|
||||
schema: schema,
|
||||
description: description ?? '',
|
||||
formatExtension: format_extension
|
||||
}
|
||||
|
||||
resourceTypeViewer.openDrawer?.()
|
||||
}}
|
||||
>
|
||||
<IconedResourceType
|
||||
after={true}
|
||||
{name}
|
||||
formatExtension={format_extension}
|
||||
/>
|
||||
</a>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<span class="text-tertiary text-xs w-96 flex flex-wrap whitespace-pre-wrap">
|
||||
{removeMarkdown(truncate(description ?? '', 200))}
|
||||
</span>
|
||||
</Cell>
|
||||
<Cell last>
|
||||
{#if !canWrite}
|
||||
<Badge>
|
||||
Shared globally
|
||||
<Tooltip>
|
||||
This resource type is from the 'admins' workspace shared with all
|
||||
workspaces
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
{:else if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
<div class="flex flex-row-reverse gap-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
btnClasses="border-0"
|
||||
startIcon={{ icon: Trash }}
|
||||
on:click={() => handleDeleteResourceType(name)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
startIcon={{ icon: Pen }}
|
||||
on:click={() => startEditResourceType(name)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Badge>
|
||||
Non Editable
|
||||
<Tooltip>
|
||||
Since resource types are shared with the whole workspace, only admins
|
||||
can edit/delete them
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
{/if}
|
||||
</Cell>
|
||||
</Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
</Scroll>
|
||||
{/if}
|
||||
|
||||
<SupabaseConnect bind:this={supabaseConnect} on:refresh={loadResources} />
|
||||
|
||||
Reference in New Issue
Block a user