From 3ca5b7c274efbd882697fd03f4d1f5a76e68b453 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 3 Dec 2022 14:23:44 +0100 Subject: [PATCH] fix(frontend): variables and resources uses tab navigation --- frontend/src/lib/components/AppConnect.svelte | 4 +- frontend/src/lib/components/Popover.svelte | 10 +- .../src/lib/components/ResourceEditor.svelte | 19 +- .../src/lib/components/TableSimple.svelte | 2 +- frontend/src/routes/resources.svelte | 548 +++++++++--------- frontend/src/routes/variables.svelte | 438 +++++++------- 6 files changed, 539 insertions(+), 482 deletions(-) diff --git a/frontend/src/lib/components/AppConnect.svelte b/frontend/src/lib/components/AppConnect.svelte index 5ffd755298..aef59f6432 100644 --- a/frontend/src/lib/components/AppConnect.svelte +++ b/frontend/src/lib/components/AppConnect.svelte @@ -378,9 +378,8 @@ -

Value

{#if apiTokenApps[resource_type]} -
Instructions
+

Instructions

    {#each apiTokenApps[resource_type].instructions as step} @@ -397,6 +396,7 @@ {/if} {/if} +

    Value

    diff --git a/frontend/src/lib/components/Popover.svelte b/frontend/src/lib/components/Popover.svelte index 0c59f3d1dc..c61a8f8463 100644 --- a/frontend/src/lib/components/Popover.svelte +++ b/frontend/src/lib/components/Popover.svelte @@ -1,5 +1,8 @@ - {#if showTooltip} diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index 89d4ce5487..144bbee84f 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -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 = {} let can_write = true + let loadingSchema = false let error: string | undefined @@ -39,15 +39,18 @@ export async function initEdit(p: string): Promise { 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 { @@ -80,7 +83,7 @@ async function loadResourceType(): Promise { 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 @@

    Value

    - {#if resourceSchema && resourceSchema?.properties} + {#if loadingSchema} + + {:else if resourceSchema && resourceSchema?.properties} -
    +
    diff --git a/frontend/src/routes/resources.svelte b/frontend/src/routes/resources.svelte index b16321a46e..95da21b140 100644 --- a/frontend/src/routes/resources.svelte +++ b/frontend/src/routes/resources.svelte @@ -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' @@ -234,286 +237,299 @@ tooltip="Save and permission rich objects (JSON) including credentials obtained through OAuth." >
    +
    -
    - {#if loading.resources} + + +
    + + Workspace +
    +
    + +
    + Resource Types 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 +
    +
    +
    + {#if tab == 'workspace'} +
    + {#if loading.resources} + + {#each new Array(6) as _} + + {/each} + {:else} + +
    + + + + + + {#if resources} + {#each resources as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired }} + + + + + + + + + {/each} + {:else if resources} + No resources to display + {:else} + Loading... + {/if} + + + {/if} + + {:else if tab == 'types'} + {#if loading.types} {#each new Array(6) as _} {/each} {:else} - - - - - - - - {#if resources} - {#each resources as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired }} - - - - + + + + + {#if resourceTypes} + {#each resourceTypes as { name, description, schema, canWrite }} + + - - + + - - - {/each} - {:else if resources} - No resources to display - {:else} - Loading... - {/if} - - + + + {/each} + {:else if resources} + No resources types to display + {:else} + Loading... + {/if} + + + {/if} - - - - - - {#if loading.types} - - {#each new Array(6) as _} - - {/each} - {:else} -
    - -
    - - - - - {#if resourceTypes} - {#each resourceTypes as { name, description, schema, canWrite }} - - - - - - {/each} - {:else if resources} - No resources types to display - {:else} - Loading... - {/if} - - - {/if} diff --git a/frontend/src/routes/variables.svelte b/frontend/src/routes/variables.svelte index dd35d48c91..b30975ef83 100644 --- a/frontend/src/routes/variables.svelte +++ b/frontend/src/routes/variables.svelte @@ -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' + x.path + ' ' + x.description} +/> + -
    - {#if loading.variables} - - {#each new Array(3) as _} - - {/each} - {:else} - -
    - - - - - - - {#each variables as { path, value, is_secret, description, extra_perms, canWrite, account, is_oauth, is_expired, refresh_error, is_linked }} - - - - - - - - - {/each} - - - {/if} - - - + + +
    + + Workspace +
    +
    + +
    + + Contextual + Contextual variables are utility variables passed to your environment when running a + script and depends on the execution context. +
    +
    +
    + {#if tab == 'workspace'} +
    + +
    +
    + {#if loading.variables} + + {#each new Array(3) as _} + + {/each} + {:else} + +
    + -
    -
    - {#if loading.contextual} - - {#each new Array(8) as _} - - {/each} - {:else} - - {/if} -
    +
    + + + + {#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_oauth, is_expired, refresh_error, is_linked }} + + + + + + + + + {/each} + + + {/if} + + {:else if tab == 'contextual'} +
    + {#if loading.contextual} + + {#each new Array(8) as _} + + {/each} + {:else} + + {/if} +
    + {/if}
    path + resource typedescription + +
    + resourceEditor?.initEdit?.(path)}>{path} + { + 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 + ) + } + }}> +
    +
    + {#if is_linked} + + +
    + This resource is linked with a variable of the same path. They are + deleted and renamed together. +
    +
    + {/if} +
    +
    + {#if account} + + +
    + The OAuth token will be kept up-to-date in the background by Windmill + using its refresh token +
    +
    + {/if} +
    + + {#if is_oauth} +
    + {#if refresh_error} + + +
    + Latest exchange of the refresh token did not succeed. Error: {refresh_error} +
    +
    + {:else if is_expired} + + +
    + 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. +
    +
    + {:else} + + +
    + The resource was connected through OAuth and the token is not + expired. +
    +
    + {/if} +
    + {/if} +
    +
    + { + 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} + /> +
    path - resource typedescription - -
    - resourceEditor?.initEdit?.(path)}>{path} - { - const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type) - if (linkedRt) { +
    + +
    namedescription +
    { 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 - ) - } - }}> -
    -
    - {#if is_linked} - - -
    - This resource is linked with a variable of the same path. They are - deleted and renamed together. -
    -
    - {/if} -
    -
    - {#if account} - - -
    - The OAuth token will be kept up-to-date in the background by Windmill - using its refresh token -
    -
    - {/if} -
    - - {#if is_oauth} -
    - {#if refresh_error} - - -
    - Latest exchange of the refresh token did not succeed. Error: {refresh_error} -
    -
    - {:else if is_expired} - - -
    - 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. -
    -
    - {:else} - - -
    - The resource was connected through OAuth and the token is not expired. -
    -
    - {/if} -
    + }}>
    + {#if !canWrite} + Shared globallyThis resource type is from the 'starter' workspace shared with all + workspaces + {:else if $userStore?.is_admin} + + {:else} + Non Editable Since resource types are shared with the whole workspace, only admins can + edit/delete them {/if} - - - { - 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} - /> -
    namedescription -
    { - resourceTypeViewerObj = { - rt: name, - schema: schema, - description: description ?? '' - } - - resourceTypeViewer.openDrawer?.() - }}> - {#if !canWrite} - Shared globallyThis resource type is from the 'starter' workspace shared with all - workspaces - {:else if $userStore?.is_admin} - - {:else} - Non Editable Since resource types are shared with the whole workspace, only admins can - edit/delete them - {/if} -
    pathvaluedescription - -
    variableEditor.editVariable(path)} - href="#{path}">{path} -
    -
    - - - {truncate(value ?? '****', 20)} - - {#if is_secret} - - - This item is secret - - {/if} - - {truncate(description ?? '', 50)} -
    -
    - {#if is_linked} - - -
    - This variable is linked with a resource of the same path. They are deleted - and renamed together. -
    -
    - {/if} -
    -
    - {#if account} - - -
    - This OAuth token will be kept up-to-date in the background by Windmill - using its refresh token -
    -
    - {/if} -
    - - {#if is_oauth} -
    - {#if refresh_error} - - -
    - Latest exchange of the refresh token did not succeed. Error: {refresh_error} -
    -
    - {:else if is_expired} - - -
    - 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. -
    -
    - {:else} - - -
    - The variable was connected through OAuth and the token is not expired. -
    -
    - {/if} -
    - {/if} -
    -
    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} - />
    pathvaluedescription + +
    variableEditor.editVariable(path)} + href="#{path}">{path} +
    +
    + + + {truncate(value ?? '****', 20)} + + {#if is_secret} + + + This item is secret + + {/if} + + {truncate(description ?? '', 50)} +
    +
    + {#if is_linked} + + +
    + This variable is linked with a resource of the same path. They are + deleted and renamed together. +
    +
    + {/if} +
    +
    + {#if account} + + +
    + This OAuth token will be kept up-to-date in the background by Windmill + using its refresh token +
    +
    + {/if} +
    + + {#if is_oauth} +
    + {#if refresh_error} + + +
    + Latest exchange of the refresh token did not succeed. Error: {refresh_error} +
    +
    + {:else if is_expired} + + +
    + 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. +
    +
    + {:else} + + +
    + The variable was connected through OAuth and the token is not expired. +
    +
    + {/if} +
    + {/if} +
    +
    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} + />