mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 03:11:12 +01:00
Add an option to allow jsonpath/xpath to return as array (#297)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ import { IconButton } from './core/IconButton';
|
||||
import { Input } from './core/Input';
|
||||
import { Label } from './core/Label';
|
||||
import { Select } from './core/Select';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { Markdown } from './Markdown';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
@@ -41,6 +41,7 @@ interface Props<T> {
|
||||
autocompleteFunctions?: boolean;
|
||||
autocompleteVariables?: boolean;
|
||||
stateKey: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
autocompleteVariables,
|
||||
autocompleteFunctions,
|
||||
stateKey,
|
||||
className,
|
||||
disabled,
|
||||
}: Props<T>) {
|
||||
const setDataAttr = useCallback(
|
||||
@@ -61,7 +63,7 @@ export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
);
|
||||
|
||||
return (
|
||||
<FormInputs
|
||||
<FormInputsStack
|
||||
disabled={disabled}
|
||||
inputs={inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
@@ -69,28 +71,15 @@ export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
autocompleteFunctions={autocompleteFunctions}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
data={data}
|
||||
className="pb-4" // Pad the bottom to look nice
|
||||
className={classNames(className, 'pb-4')} // Pad the bottom to look nice
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
autocompleteFunctions,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
setDataAttr,
|
||||
data,
|
||||
disabled,
|
||||
function FormInputsStack<T extends Record<string, JsonPrimitive>>({
|
||||
className,
|
||||
}: Pick<
|
||||
Props<T>,
|
||||
'inputs' | 'autocompleteFunctions' | 'autocompleteVariables' | 'stateKey' | 'data'
|
||||
> & {
|
||||
setDataAttr: (name: string, value: JsonPrimitive) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
...props
|
||||
}: FormInputsProps<T> & { className?: string }) {
|
||||
return (
|
||||
<VStack
|
||||
space={3}
|
||||
@@ -100,6 +89,30 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
'pr-1', // A bit of space between inputs and scrollbar
|
||||
)}
|
||||
>
|
||||
<FormInputs {...props} />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
type FormInputsProps<T> = Pick<
|
||||
Props<T>,
|
||||
'inputs' | 'autocompleteFunctions' | 'autocompleteVariables' | 'stateKey' | 'data'
|
||||
> & {
|
||||
setDataAttr: (name: string, value: JsonPrimitive) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
autocompleteFunctions,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
setDataAttr,
|
||||
data,
|
||||
disabled,
|
||||
}: FormInputsProps<T>) {
|
||||
return (
|
||||
<>
|
||||
{inputs?.map((input, i) => {
|
||||
if ('hidden' in input && input.hidden) {
|
||||
return null;
|
||||
@@ -113,7 +126,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
case 'select':
|
||||
return (
|
||||
<SelectArg
|
||||
key={i + stateKey}
|
||||
key={i}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
@@ -126,7 +139,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
case 'text':
|
||||
return (
|
||||
<TextArg
|
||||
key={i}
|
||||
key={i + stateKey}
|
||||
stateKey={stateKey}
|
||||
arg={input}
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
@@ -140,7 +153,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
case 'editor':
|
||||
return (
|
||||
<EditorArg
|
||||
key={i}
|
||||
key={i + stateKey}
|
||||
stateKey={stateKey}
|
||||
arg={input}
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
@@ -182,13 +195,13 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
);
|
||||
case 'accordion':
|
||||
return (
|
||||
<div key={i}>
|
||||
<div key={i + stateKey}>
|
||||
<DetailsBanner
|
||||
summary={input.label}
|
||||
className={classNames('!mb-auto', disabled && 'opacity-disabled')}
|
||||
>
|
||||
<div className="my-3">
|
||||
<FormInputs
|
||||
<FormInputsStack
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
@@ -201,14 +214,28 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
</DetailsBanner>
|
||||
</div>
|
||||
);
|
||||
case 'h_stack':
|
||||
return (
|
||||
<HStack key={i + stateKey} alignItems="end" space={3}>
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
case 'banner':
|
||||
return (
|
||||
<Banner
|
||||
key={i}
|
||||
key={i + stateKey}
|
||||
color={input.color}
|
||||
className={classNames(disabled && 'opacity-disabled')}
|
||||
>
|
||||
<FormInputs
|
||||
<FormInputsStack
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
@@ -220,10 +247,10 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
</Banner>
|
||||
);
|
||||
case 'markdown':
|
||||
return <Markdown>{input.content}</Markdown>;
|
||||
return <Markdown key={i + stateKey}>{input.content}</Markdown>;
|
||||
}
|
||||
})}
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,7 +282,7 @@ function TextArg({
|
||||
type={arg.password ? 'password' : 'text'}
|
||||
label={arg.label ?? arg.name}
|
||||
size={INPUT_SIZE}
|
||||
hideLabel={arg.label == null}
|
||||
hideLabel={arg.hideLabel ?? arg.label == null}
|
||||
placeholder={arg.placeholder ?? undefined}
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
autocompleteFunctions={autocompleteFunctions}
|
||||
@@ -313,7 +340,9 @@ function EditorArg({
|
||||
language={arg.language}
|
||||
readOnly={arg.readOnly}
|
||||
onChange={onChange}
|
||||
hideGutter
|
||||
heightMode="auto"
|
||||
className="min-h-[3rem]"
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
placeholder={arg.placeholder ?? undefined}
|
||||
autocompleteFunctions={autocompleteFunctions}
|
||||
@@ -374,7 +403,6 @@ function EditorArg({
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
hideGutter
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -396,6 +424,7 @@ function SelectArg({
|
||||
name={arg.name}
|
||||
help={arg.description}
|
||||
onChange={onChange}
|
||||
defaultValue={arg.defaultValue}
|
||||
hideLabel={arg.hideLabel}
|
||||
value={value}
|
||||
size={INPUT_SIZE}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import type { FormInput, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import type { FnArg, Tokens } from '@yaakapp-internal/templates';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
@@ -45,24 +45,7 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
}
|
||||
|
||||
(async function () {
|
||||
const initial: Record<string, string> = {};
|
||||
const initialArgs =
|
||||
initialTokens.tokens[0]?.type === 'tag' && initialTokens.tokens[0]?.val.type === 'fn'
|
||||
? initialTokens.tokens[0]?.val.args
|
||||
: [];
|
||||
for (const arg of templateFunction.args) {
|
||||
if (!('name' in arg)) {
|
||||
// Skip visual-only args
|
||||
continue;
|
||||
}
|
||||
const initialArg = initialArgs.find((a) => a.name === arg.name);
|
||||
const initialArgValue =
|
||||
initialArg?.value.type === 'str'
|
||||
? initialArg?.value.text
|
||||
: // TODO: Implement variable-based args
|
||||
undefined;
|
||||
initial[arg.name] = initialArgValue ?? arg.defaultValue ?? DYNAMIC_FORM_NULL_ARG;
|
||||
}
|
||||
const initial = collectArgumentValues(initialTokens, templateFunction);
|
||||
|
||||
// HACK: Replace the secure() function's encrypted `value` arg with the decrypted version so
|
||||
// we can display it in the editor input.
|
||||
@@ -71,12 +54,14 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
initial.value = await convertTemplateToInsecure(template);
|
||||
}
|
||||
|
||||
console.log('INITIAL', initial);
|
||||
setInitialArgValues(initial);
|
||||
})().catch(console.error);
|
||||
}, [
|
||||
initialArgValues,
|
||||
initialTokens,
|
||||
initialTokens.tokens,
|
||||
templateFunction,
|
||||
templateFunction.args,
|
||||
templateFunction.name,
|
||||
]);
|
||||
@@ -159,84 +144,117 @@ function InitializedTemplateFunctionDialog({
|
||||
if (templateFunction == null) return null;
|
||||
|
||||
return (
|
||||
<VStack
|
||||
as="form"
|
||||
className="pb-3"
|
||||
space={4}
|
||||
<form
|
||||
className="grid grid-rows-[minmax(0,1fr)_auto_auto] h-full max-h-[90vh]"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleDone();
|
||||
}}
|
||||
>
|
||||
{name === 'secure' ? (
|
||||
<PlainInput
|
||||
required
|
||||
label="Value"
|
||||
name="value"
|
||||
type="password"
|
||||
placeholder="••••••••••••"
|
||||
defaultValue={String(argValues['value'] ?? '')}
|
||||
onChange={(value) => setArgValues({ ...argValues, value })}
|
||||
/>
|
||||
) : (
|
||||
<DynamicForm
|
||||
autocompleteVariables
|
||||
autocompleteFunctions
|
||||
inputs={templateFunction.args}
|
||||
data={argValues}
|
||||
onChange={setArgValues}
|
||||
stateKey={`template_function.${templateFunction.name}`}
|
||||
/>
|
||||
)}
|
||||
{enablePreview && (
|
||||
<VStack className="w-full" space={1}>
|
||||
<HStack space={0.5}>
|
||||
<HStack className="text-sm text-text-subtle" space={1.5}>
|
||||
Rendered Preview
|
||||
{rendered.isPending && <LoadingIcon size="xs" />}
|
||||
</HStack>
|
||||
<IconButton
|
||||
size="xs"
|
||||
iconSize="sm"
|
||||
icon={showSecretsInPreview ? 'lock' : 'lock_open'}
|
||||
title={showSecretsInPreview ? 'Show preview' : 'Hide preview'}
|
||||
onClick={toggleShowSecretsInPreview}
|
||||
className={classNames(
|
||||
'ml-auto text-text-subtlest',
|
||||
!dataContainsSecrets && 'invisible',
|
||||
)}
|
||||
/>
|
||||
</HStack>
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
'whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars',
|
||||
tooLarge && 'italic text-danger',
|
||||
)}
|
||||
>
|
||||
{rendered.error || tagText.error ? (
|
||||
<em className="text-danger">
|
||||
{`${rendered.error || tagText.error}`.replace(/^Render Error: /, '')}
|
||||
</em>
|
||||
) : dataContainsSecrets && !showSecretsInPreview ? (
|
||||
<span className="italic text-text-subtle">------ sensitive values hidden ------</span>
|
||||
) : tooLarge ? (
|
||||
'too large to preview'
|
||||
) : (
|
||||
rendered.data || <> </>
|
||||
)}
|
||||
</InlineCode>
|
||||
</VStack>
|
||||
)}
|
||||
<div className="flex justify-stretch w-full flex-grow gap-2 [&>*]:flex-1">
|
||||
{templateFunction.name === 'secure' && (
|
||||
<Button variant="border" color="secondary" onClick={setupOrConfigureEncryption}>
|
||||
Reveal Encryption Key
|
||||
</Button>
|
||||
<div className="overflow-y-auto h-full px-6">
|
||||
{name === 'secure' ? (
|
||||
<PlainInput
|
||||
required
|
||||
label="Value"
|
||||
name="value"
|
||||
type="password"
|
||||
placeholder="••••••••••••"
|
||||
defaultValue={String(argValues['value'] ?? '')}
|
||||
onChange={(value) => setArgValues({ ...argValues, value })}
|
||||
/>
|
||||
) : (
|
||||
<DynamicForm
|
||||
autocompleteVariables
|
||||
autocompleteFunctions
|
||||
inputs={templateFunction.args}
|
||||
data={argValues}
|
||||
onChange={setArgValues}
|
||||
stateKey={`template_function.${templateFunction.name}`}
|
||||
/>
|
||||
)}
|
||||
<Button type="submit" color="primary">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</VStack>
|
||||
<div className="px-6 border-t border-t-border py-3 bg-surface-highlight w-full flex flex-col gap-4">
|
||||
{enablePreview ? (
|
||||
<VStack className="w-full">
|
||||
<HStack space={0.5}>
|
||||
<HStack className="text-sm text-text-subtle" space={1.5}>
|
||||
Rendered Preview
|
||||
{rendered.isPending && <LoadingIcon size="xs" />}
|
||||
</HStack>
|
||||
<IconButton
|
||||
size="xs"
|
||||
iconSize="sm"
|
||||
icon={showSecretsInPreview ? 'lock' : 'lock_open'}
|
||||
title={showSecretsInPreview ? 'Show preview' : 'Hide preview'}
|
||||
onClick={toggleShowSecretsInPreview}
|
||||
className={classNames(
|
||||
'ml-auto text-text-subtlest',
|
||||
!dataContainsSecrets && 'invisible',
|
||||
)}
|
||||
/>
|
||||
</HStack>
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
'whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars !border-text-subtlest',
|
||||
tooLarge && 'italic text-danger',
|
||||
)}
|
||||
>
|
||||
{rendered.error || tagText.error ? (
|
||||
<em className="text-danger">
|
||||
{`${rendered.error || tagText.error}`.replace(/^Render Error: /, '')}
|
||||
</em>
|
||||
) : dataContainsSecrets && !showSecretsInPreview ? (
|
||||
<span className="italic text-text-subtle">
|
||||
------ sensitive values hidden ------
|
||||
</span>
|
||||
) : tooLarge ? (
|
||||
'too large to preview'
|
||||
) : (
|
||||
rendered.data || <> </>
|
||||
)}
|
||||
</InlineCode>
|
||||
</VStack>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<div className="flex justify-stretch w-full flex-grow gap-2 [&>*]:flex-1">
|
||||
{templateFunction.name === 'secure' && (
|
||||
<Button variant="border" color="secondary" onClick={setupOrConfigureEncryption}>
|
||||
Reveal Encryption Key
|
||||
</Button>
|
||||
)}
|
||||
<Button type="submit" color="primary">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the initial tokens from the template and merge those with the default values pulled from
|
||||
* the template function definition.
|
||||
*/
|
||||
function collectArgumentValues(initialTokens: Tokens, templateFunction: TemplateFunction) {
|
||||
const initial: Record<string, string | boolean> = {};
|
||||
const initialArgs =
|
||||
initialTokens.tokens[0]?.type === 'tag' && initialTokens.tokens[0]?.val.type === 'fn'
|
||||
? initialTokens.tokens[0]?.val.args
|
||||
: [];
|
||||
|
||||
const processArg = (arg: FormInput) => {
|
||||
if ('inputs' in arg && arg.inputs) {
|
||||
arg.inputs.forEach(processArg);
|
||||
}
|
||||
if (!('name' in arg)) return;
|
||||
|
||||
const initialArg = initialArgs.find((a) => a.name === arg.name);
|
||||
const initialArgValue = initialArg?.value.type === 'str' ? initialArg?.value.text : undefined;
|
||||
initial[arg.name] = initialArgValue ?? arg.defaultValue ?? DYNAMIC_FORM_NULL_ARG;
|
||||
};
|
||||
|
||||
templateFunction.args.forEach(processArg);
|
||||
|
||||
return initial;
|
||||
}
|
||||
|
||||
@@ -655,7 +655,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
className,
|
||||
'h-xs', // More compact
|
||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap',
|
||||
'focus:bg-surface-highlight focus:text rounded',
|
||||
'focus:bg-surface-highlight focus:text rounded focus:outline-none focus-visible:outline-1',
|
||||
item.color === 'danger' && '!text-danger',
|
||||
item.color === 'primary' && '!text-primary',
|
||||
item.color === 'success' && '!text-success',
|
||||
|
||||
@@ -78,9 +78,19 @@
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cm-gutter-lint {
|
||||
@apply w-auto !important;
|
||||
|
||||
.cm-gutterElement {
|
||||
@apply px-0;
|
||||
}
|
||||
|
||||
.cm-lint-marker {
|
||||
@apply cursor-default opacity-80 hover:opacity-100 transition-opacity;
|
||||
@apply rounded-full w-[0.9em] h-[0.9em];
|
||||
|
||||
content: '';
|
||||
|
||||
&.cm-lint-marker-error {
|
||||
|
||||
@@ -290,6 +290,8 @@ export function Editor({
|
||||
showDialog({
|
||||
id: 'template-function-' + Math.random(), // Allow multiple at once
|
||||
size: 'md',
|
||||
className: 'h-[90vh]',
|
||||
noPadding: true,
|
||||
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
||||
description: fn.description,
|
||||
render: ({ hide }) => {
|
||||
@@ -354,6 +356,7 @@ export function Editor({
|
||||
const ext = getLanguageExtension({
|
||||
useTemplating,
|
||||
language,
|
||||
hideGutter,
|
||||
environmentVariables,
|
||||
autocomplete,
|
||||
completionOptions,
|
||||
@@ -374,6 +377,7 @@ export function Editor({
|
||||
completionOptions,
|
||||
useTemplating,
|
||||
graphQLSchema,
|
||||
hideGutter,
|
||||
]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
|
||||
@@ -105,6 +105,7 @@ export function getLanguageExtension({
|
||||
language = 'text',
|
||||
environmentVariables,
|
||||
autocomplete,
|
||||
hideGutter,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
@@ -118,7 +119,7 @@ export function getLanguageExtension({
|
||||
onClickPathParameter: (name: string) => void;
|
||||
completionOptions: TwigCompletionOption[];
|
||||
graphQLSchema: GraphQLSchema | null;
|
||||
} & Pick<EditorProps, 'language' | 'autocomplete'>) {
|
||||
} & Pick<EditorProps, 'language' | 'autocomplete' | 'hideGutter'>) {
|
||||
const extraExtensions: Extension[] = [];
|
||||
|
||||
if (language === 'url') {
|
||||
@@ -155,7 +156,10 @@ export function getLanguageExtension({
|
||||
}
|
||||
|
||||
if (language === 'json') {
|
||||
extraExtensions.push(linter(jsonParseLinter()), lintGutter());
|
||||
extraExtensions.push(linter(jsonParseLinter()));
|
||||
if (!hideGutter) {
|
||||
extraExtensions.push(lintGutter());
|
||||
}
|
||||
}
|
||||
|
||||
const maybeBase = language ? syntaxExtensions[language] : null;
|
||||
|
||||
@@ -47,7 +47,7 @@ export function useHttpAuthenticationConfig(
|
||||
],
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
if (authName == null) return null;
|
||||
if (authName == null || authName === 'inherit') return null;
|
||||
const config = await invokeCmd<GetHttpAuthenticationConfigResponse>(
|
||||
'cmd_get_http_authentication_config',
|
||||
{
|
||||
|
||||
@@ -45,16 +45,25 @@ export function useTemplateFunctionConfig(
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
if (functionName == null) return null;
|
||||
const config = await invokeCmd<GetTemplateFunctionConfigResponse>(
|
||||
'cmd_template_function_config',
|
||||
{
|
||||
functionName: functionName,
|
||||
values,
|
||||
model,
|
||||
environmentId,
|
||||
},
|
||||
);
|
||||
return config.function;
|
||||
return getTemplateFunctionConfig(functionName, values, model, environmentId);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTemplateFunctionConfig(
|
||||
functionName: string,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||
environmentId: string | undefined,
|
||||
) {
|
||||
const config = await invokeCmd<GetTemplateFunctionConfigResponse>(
|
||||
'cmd_template_function_config',
|
||||
{
|
||||
functionName,
|
||||
values,
|
||||
model,
|
||||
environmentId,
|
||||
},
|
||||
);
|
||||
return config.function;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function editEnvironment(
|
||||
id: 'environment-editor',
|
||||
noPadding: true,
|
||||
size: 'lg',
|
||||
className: 'h-[80vh]',
|
||||
className: 'h-[90vh]',
|
||||
render: () => (
|
||||
<EnvironmentEditDialog
|
||||
initialEnvironmentId={environment?.id ?? null}
|
||||
|
||||
Reference in New Issue
Block a user