mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
feat(app): add user resource select component
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
import AppConnectInner from './AppConnectInner.svelte'
|
||||
import DarkModeObserver from './DarkModeObserver.svelte'
|
||||
|
||||
let expressOAuthSetup = false
|
||||
|
||||
let drawer: Drawer
|
||||
let resourceType = ''
|
||||
let step = 1
|
||||
@@ -16,12 +18,17 @@
|
||||
let appConnectInner: AppConnectInner | undefined = undefined
|
||||
|
||||
let rtToLoad: string | undefined = ''
|
||||
export async function open(rt?: string) {
|
||||
export async function open(rt?: string, express?: boolean) {
|
||||
expressOAuthSetup = express ?? false
|
||||
rtToLoad = rt
|
||||
drawer.openDrawer?.()
|
||||
}
|
||||
|
||||
$: appConnectInner?.open(rtToLoad)
|
||||
$: appConnectInner && onRtToLoadChange(rtToLoad)
|
||||
|
||||
function onRtToLoadChange(rtToLoad: string | undefined) {
|
||||
appConnectInner?.open(rtToLoad, expressOAuthSetup)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import {
|
||||
OauthService,
|
||||
@@ -75,11 +75,14 @@
|
||||
|
||||
let pathError = ''
|
||||
|
||||
export async function open(rt?: string) {
|
||||
let expressOAuthSetup = false
|
||||
|
||||
export async function open(rt?: string, express?: boolean) {
|
||||
expressOAuthSetup = express ?? false
|
||||
if (!rt) {
|
||||
loadResourceTypes()
|
||||
}
|
||||
step = 1
|
||||
step = 1 //express && !manual ? 3 : 1
|
||||
value = ''
|
||||
description = ''
|
||||
resourceType = rt ?? ''
|
||||
@@ -87,6 +90,10 @@
|
||||
await loadConnects()
|
||||
manual = !connects?.includes(resourceType)
|
||||
if (rt) {
|
||||
if (!manual && expressOAuthSetup) {
|
||||
await getScopesAndParams()
|
||||
step = 2
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -167,6 +174,10 @@
|
||||
value = data.res.access_token!
|
||||
valueToken = data.res
|
||||
step = 4
|
||||
if (expressOAuthSetup) {
|
||||
path = `u/${$userStore?.username}/${resourceType}_${new Date().getTime()}`
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
export let value: string | undefined = initialValue
|
||||
export let valueType: string | undefined = undefined
|
||||
export let resourceType: string | undefined = undefined
|
||||
export let disabled = false
|
||||
export let disablePortal = false
|
||||
export let showSchemaExplorer = false
|
||||
export let selectFirst = false
|
||||
export let expressOAuthSetup = false
|
||||
|
||||
let valueSelect =
|
||||
initialValue || value
|
||||
@@ -36,6 +38,10 @@
|
||||
|
||||
let collection = valueSelect ? [valueSelect] : []
|
||||
|
||||
export async function askNewResource() {
|
||||
appConnect?.open?.(resourceType)
|
||||
}
|
||||
|
||||
async function loadResources(resourceType: string | undefined) {
|
||||
const nc = (
|
||||
await ResourceService.listResource({
|
||||
@@ -101,31 +107,37 @@
|
||||
/>
|
||||
|
||||
<div class="flex flex-col w-full items-start">
|
||||
<div class="flex flex-row gap-x-1 w-full">
|
||||
<Select
|
||||
portal={!disablePortal}
|
||||
value={valueSelect}
|
||||
on:change={(e) => {
|
||||
value = e.detail.value
|
||||
valueType = e.detail.type
|
||||
valueSelect = e.detail
|
||||
}}
|
||||
on:clear={() => {
|
||||
value = undefined
|
||||
valueType = undefined
|
||||
valueSelect = undefined
|
||||
}}
|
||||
items={collection}
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder="{resourceType ?? 'any'} resource"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={darkMode
|
||||
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
|
||||
: SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
/>
|
||||
<div class="flex flex-row gap-x-1 w-full items-center">
|
||||
{#if collection?.length > 0}
|
||||
<Select
|
||||
{disabled}
|
||||
portal={!disablePortal}
|
||||
value={valueSelect}
|
||||
on:change={(e) => {
|
||||
value = e.detail.value
|
||||
valueType = e.detail.type
|
||||
valueSelect = e.detail
|
||||
}}
|
||||
on:clear={() => {
|
||||
value = undefined
|
||||
valueType = undefined
|
||||
valueSelect = undefined
|
||||
}}
|
||||
items={collection}
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder="{resourceType ?? 'any'} resource"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={darkMode
|
||||
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
|
||||
: SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
/>
|
||||
{:else}
|
||||
<div class="text-2xs text-tertiary mr-2">0 found</div>
|
||||
{/if}
|
||||
|
||||
{#if value && value != ''}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
@@ -138,6 +150,7 @@
|
||||
{#if resourceType?.includes(',')}
|
||||
{#each resourceType.split(',') as rt}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
@@ -147,13 +160,17 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => appConnect?.open?.(resourceType)}
|
||||
on:click={() => appConnect?.open?.(resourceType, expressOAuthSetup)}
|
||||
startIcon={{ icon: Plus }}
|
||||
iconOnly
|
||||
/>
|
||||
iconOnly={collection?.length > 0}
|
||||
>{#if collection?.length == 0}
|
||||
Add a {resourceType} resource
|
||||
{/if}</Button
|
||||
>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
|
||||
@@ -25,7 +25,7 @@ function create_context_function_template(
|
||||
) {
|
||||
let hasReturnAsLastLine = noReturn || eval_string.split('\n').some((x) => x.startsWith('return '))
|
||||
return `
|
||||
return async function (context, state, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, showToast, waitJob) {
|
||||
return async function (context, state, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, showToast, waitJob, askNewResource) {
|
||||
"use strict";
|
||||
${
|
||||
contextKeys && contextKeys.length > 0
|
||||
@@ -61,7 +61,8 @@ type WmFunctor = (
|
||||
validateAll,
|
||||
clearFiles,
|
||||
showToast,
|
||||
waitJob
|
||||
waitJob,
|
||||
askNewResource
|
||||
) => Promise<any>
|
||||
|
||||
let functorCache: Record<number, WmFunctor> = {}
|
||||
@@ -111,6 +112,7 @@ export async function eval_like(
|
||||
clearFiles?: () => void
|
||||
showToast?: (message: string, error?: boolean) => void
|
||||
waitJob?: (jobId: string) => void
|
||||
askNewResource?: () => void
|
||||
}
|
||||
>,
|
||||
worldStore: World | undefined,
|
||||
@@ -189,6 +191,9 @@ export async function eval_like(
|
||||
(message, error) => {
|
||||
sendUserToast(message, error)
|
||||
},
|
||||
async (id) => waitJob(id)
|
||||
async (id) => waitJob(id),
|
||||
(id) => {
|
||||
controlComponents[id]?.askNewResource?.()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
$componentControl[id] = {
|
||||
setValue(nvalue: string) {
|
||||
value = nvalue
|
||||
},
|
||||
askNewResource() {
|
||||
resourcePicker?.askNewResource()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,15 +58,17 @@
|
||||
if (!value || value === '') {
|
||||
nval = undefined
|
||||
} else {
|
||||
nval = '$res:' + value
|
||||
nval = '$res:' + value.replace('$res:', '')
|
||||
}
|
||||
outputs?.result.set(nval)
|
||||
if (iterContext && listInputs) {
|
||||
listInputs.set(id, value)
|
||||
listInputs.set(id, nval)
|
||||
}
|
||||
}
|
||||
|
||||
let css = initCss($app.css?.['userresourcecomponent'], customCss)
|
||||
|
||||
let resourcePicker: ResourcePicker | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['userresourcecomponent'].initialData.configuration) as key (key)}
|
||||
@@ -90,6 +95,8 @@
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
<div class="relative w-full">
|
||||
<ResourcePicker
|
||||
expressOAuthSetup={resolvedConfig.expressOauthSetup}
|
||||
bind:this={resourcePicker}
|
||||
{value}
|
||||
on:change={(e) => {
|
||||
assignValue(e.detail)
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
import AppDateSelect from '../../components/inputs/AppDateSelect.svelte'
|
||||
import AppDisplayComponentByJobId from '../../components/display/AppRecomputeAll.svelte'
|
||||
import AppRecomputeAll from '../../components/display/AppRecomputeAll.svelte'
|
||||
import AppUserResource from '../../components/inputs/AppUserResource.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -434,6 +435,14 @@
|
||||
onSelect={component.onSelect}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'userresourcecomponent'}
|
||||
<AppUserResource
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'multiselectcomponent'}
|
||||
<AppMultiSelect
|
||||
id={component.id}
|
||||
|
||||
@@ -181,6 +181,10 @@ export type ResourceSelectComponent = BaseComponent<'resourceselectcomponent'> &
|
||||
RecomputeOthersSource & {
|
||||
onSelect?: string[]
|
||||
}
|
||||
export type ResourceConnectComponent = BaseComponent<'userresourcecomponent'> &
|
||||
RecomputeOthersSource & {
|
||||
onSelect?: string[]
|
||||
}
|
||||
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
|
||||
export type MultiSelectComponentV2 = BaseComponent<'multiselectcomponentv2'>
|
||||
export type CheckboxComponent = BaseComponent<'checkboxcomponent'> &
|
||||
@@ -361,6 +365,7 @@ export type TypedComponent =
|
||||
| DateSelectComponent
|
||||
| JobIdDisplayComponent
|
||||
| RecomputeAllComponent
|
||||
| ResourceConnectComponent
|
||||
|
||||
export type AppComponent = BaseAppComponent & TypedComponent
|
||||
|
||||
@@ -2216,7 +2221,7 @@ This is a paragraph.
|
||||
}
|
||||
},
|
||||
resourceselectcomponent: {
|
||||
name: 'Resource Select',
|
||||
name: 'Static Resource Select',
|
||||
icon: List,
|
||||
documentationLink: `${documentationBaseUrl}/resource_select`,
|
||||
dims: '2:1-3:1' as AppComponentDimensions,
|
||||
@@ -2254,6 +2259,38 @@ This is a paragraph.
|
||||
}
|
||||
}
|
||||
},
|
||||
userresourcecomponent: {
|
||||
name: 'User Resource Input',
|
||||
icon: List,
|
||||
documentationLink: `${documentationBaseUrl}/resource_select`,
|
||||
dims: '2:1-3:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
input: { style: '' }
|
||||
},
|
||||
initialData: {
|
||||
verticalAlignment: 'center',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
resourceType: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'postgresql'
|
||||
},
|
||||
expressOauthSetup: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
tooltip:
|
||||
'If enabled, skip some steps while adding oauth resources: No scopes to set prior to OAuth sign-in, and no path to set after OAuth sign-in'
|
||||
},
|
||||
disabled: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
numberinputcomponent: {
|
||||
name: 'Number',
|
||||
icon: Binary,
|
||||
|
||||
@@ -54,6 +54,7 @@ const inputs: ComponentSet = {
|
||||
'checkboxcomponent',
|
||||
'selectcomponent',
|
||||
'resourceselectcomponent',
|
||||
'userresourcecomponent',
|
||||
'multiselectcomponentv2',
|
||||
'selecttabcomponent',
|
||||
'selectstepcomponent'
|
||||
|
||||
@@ -748,6 +748,9 @@ export const quickStyleProperties: Record<
|
||||
resourceselectcomponent: {
|
||||
input: inputDefaultProps
|
||||
},
|
||||
userresourcecomponent: {
|
||||
input: inputDefaultProps
|
||||
},
|
||||
verticaldividercomponent: {
|
||||
divider: dividerDefaultProps,
|
||||
container: containerDefaultProps
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
'formbuttoncomponent',
|
||||
'checkboxcomponent',
|
||||
'resourceselectcomponent',
|
||||
'userresourcecomponent',
|
||||
'selectcomponent',
|
||||
'tabscomponent',
|
||||
'conditionalwrapper',
|
||||
@@ -64,7 +65,7 @@
|
||||
bind:value={item.data.onToggle}
|
||||
/>
|
||||
{/if}
|
||||
{#if item.data.type === 'resourceselectcomponent' || item.data.type === 'selectcomponent'}
|
||||
{#if item.data.type === 'resourceselectcomponent' || item.data.type === 'selectcomponent' || item.data.type == 'userresourcecomponent'}
|
||||
<EventHandlerItem
|
||||
title="on select"
|
||||
tooltip="Contrary to onSuccess, this will only trigger recompute when a human select an item, not if it set by a default value or by setValue"
|
||||
|
||||
@@ -262,6 +262,7 @@ export type AppViewerContext = {
|
||||
clearFiles?: () => void
|
||||
showToast?: (message: string, error?: boolean) => void
|
||||
recompute?: () => void
|
||||
askNewResource?: () => void
|
||||
}
|
||||
>
|
||||
>
|
||||
|
||||
@@ -267,6 +267,11 @@ declare function showToast(message: string, error?: boolean): void;
|
||||
declare async function waitJob(id: string): Promise<any>;
|
||||
|
||||
|
||||
/**
|
||||
* ask user resource on a UserResourceComponent
|
||||
*/
|
||||
declare async function askNewResource(id: string): void;
|
||||
|
||||
`
|
||||
: ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user