mirror of
https://github.com/mountain-loop/yaak.git
synced 2025-12-05 19:17:44 -06:00
Conditionally disable auth
This commit is contained in:
@@ -131,7 +131,6 @@ export const plugin: PluginDefinition = {
|
||||
type: 'select',
|
||||
name: 'grantType',
|
||||
label: 'Grant Type',
|
||||
hideLabel: true,
|
||||
defaultValue: defaultGrantType,
|
||||
options: grantTypes,
|
||||
},
|
||||
|
||||
@@ -118,6 +118,7 @@ async fn cmd_render_template<R: Runtime>(
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
purpose: Option<RenderPurpose>,
|
||||
ignore_error: bool,
|
||||
) -> YaakResult<String> {
|
||||
let environment_chain =
|
||||
app_handle.db().resolve_environments(workspace_id, None, environment_id)?;
|
||||
@@ -130,7 +131,11 @@ async fn cmd_render_template<R: Runtime>(
|
||||
purpose.unwrap_or(RenderPurpose::Preview),
|
||||
),
|
||||
&RenderOptions {
|
||||
error_behavior: RenderErrorBehavior::Throw,
|
||||
error_behavior: if ignore_error {
|
||||
RenderErrorBehavior::ReturnEmpty
|
||||
} else {
|
||||
RenderErrorBehavior::Throw
|
||||
},
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use log::info;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use yaak_http::path_placeholders::apply_path_placeholders;
|
||||
@@ -5,7 +6,7 @@ use yaak_models::models::{
|
||||
Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
|
||||
};
|
||||
use yaak_models::render::make_vars_hashmap;
|
||||
use yaak_templates::{parse_and_render, render_json_value_raw, RenderOptions, TemplateCallback};
|
||||
use yaak_templates::{RenderOptions, TemplateCallback, parse_and_render, render_json_value_raw};
|
||||
|
||||
pub async fn render_template<T: TemplateCallback>(
|
||||
template: &str,
|
||||
@@ -45,10 +46,37 @@ pub async fn render_grpc_request<T: TemplateCallback>(
|
||||
})
|
||||
}
|
||||
|
||||
let mut authentication = BTreeMap::new();
|
||||
for (k, v) in r.authentication.clone() {
|
||||
authentication.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
let authentication = {
|
||||
let mut disabled = false;
|
||||
let mut auth = BTreeMap::new();
|
||||
match r.authentication.get("disabled") {
|
||||
Some(Value::Bool(true)) => {
|
||||
disabled = true;
|
||||
}
|
||||
Some(Value::String(tmpl)) => {
|
||||
disabled = parse_and_render(tmpl.as_str(), vars, cb, &opt)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.is_empty();
|
||||
info!(
|
||||
"Rendering authentication.disabled as a template: {disabled} from \"{tmpl}\""
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if disabled {
|
||||
auth.insert("disabled".to_string(), Value::Bool(true));
|
||||
} else {
|
||||
for (k, v) in r.authentication.clone() {
|
||||
if k == "disabled" {
|
||||
auth.insert(k, Value::Bool(false));
|
||||
} else {
|
||||
auth.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
auth
|
||||
};
|
||||
|
||||
let url = parse_and_render(r.url.as_str(), vars, cb, &opt).await?;
|
||||
|
||||
@@ -99,10 +127,37 @@ pub async fn render_http_request<T: TemplateCallback>(
|
||||
body.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
|
||||
let mut authentication = BTreeMap::new();
|
||||
for (k, v) in r.authentication.clone() {
|
||||
authentication.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
let authentication = {
|
||||
let mut disabled = false;
|
||||
let mut auth = BTreeMap::new();
|
||||
match r.authentication.get("disabled") {
|
||||
Some(Value::Bool(true)) => {
|
||||
disabled = true;
|
||||
}
|
||||
Some(Value::String(tmpl)) => {
|
||||
disabled = parse_and_render(tmpl.as_str(), vars, cb, &opt)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.is_empty();
|
||||
info!(
|
||||
"Rendering authentication.disabled as a template: {disabled} from \"{tmpl}\""
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if disabled {
|
||||
auth.insert("disabled".to_string(), Value::Bool(true));
|
||||
} else {
|
||||
for (k, v) in r.authentication.clone() {
|
||||
if k == "disabled" {
|
||||
auth.insert(k, Value::Bool(false));
|
||||
} else {
|
||||
auth.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
auth
|
||||
};
|
||||
|
||||
let url = parse_and_render(r.url.clone().as_str(), vars, cb, &opt).await?;
|
||||
|
||||
|
||||
@@ -155,7 +155,6 @@ impl EncryptionManager {
|
||||
let raw_key = mkey
|
||||
.decrypt(decoded_key.as_slice())
|
||||
.map_err(|e| WorkspaceKeyDecryptionError(e.to_string()))?;
|
||||
info!("Got existing workspace key for {workspace_id}");
|
||||
let wkey = WorkspaceKey::from_raw_key(raw_key.as_slice());
|
||||
|
||||
Ok(wkey)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::error::Result;
|
||||
use log::info;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use yaak_models::models::{Environment, HttpRequestHeader, HttpUrlParameter, WebsocketRequest};
|
||||
use yaak_models::render::make_vars_hashmap;
|
||||
use yaak_templates::{parse_and_render, render_json_value_raw, RenderOptions, TemplateCallback};
|
||||
use yaak_templates::{RenderOptions, TemplateCallback, parse_and_render, render_json_value_raw};
|
||||
|
||||
pub async fn render_websocket_request<T: TemplateCallback>(
|
||||
r: &WebsocketRequest,
|
||||
@@ -32,10 +34,37 @@ pub async fn render_websocket_request<T: TemplateCallback>(
|
||||
})
|
||||
}
|
||||
|
||||
let mut authentication = BTreeMap::new();
|
||||
for (k, v) in r.authentication.clone() {
|
||||
authentication.insert(k, render_json_value_raw(v, vars, cb, opt).await?);
|
||||
}
|
||||
let authentication = {
|
||||
let mut disabled = false;
|
||||
let mut auth = BTreeMap::new();
|
||||
match r.authentication.get("disabled") {
|
||||
Some(Value::Bool(true)) => {
|
||||
disabled = true;
|
||||
}
|
||||
Some(Value::String(tmpl)) => {
|
||||
disabled = parse_and_render(tmpl.as_str(), vars, cb, &opt)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.is_empty();
|
||||
info!(
|
||||
"Rendering authentication.disabled as a template: {disabled} from \"{tmpl}\""
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if disabled {
|
||||
auth.insert("disabled".to_string(), Value::Bool(true));
|
||||
} else {
|
||||
for (k, v) in r.authentication.clone() {
|
||||
if k == "disabled" {
|
||||
auth.insert(k, Value::Bool(false));
|
||||
} else {
|
||||
auth.insert(k, render_json_value_raw(v, vars, cb, &opt).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
auth
|
||||
};
|
||||
|
||||
let url = parse_and_render(r.url.as_str(), vars, cb, opt).await?;
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import { useCallback } from 'react';
|
||||
import { openFolderSettings } from '../commands/openFolderSettings';
|
||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||
import { useInheritedAuthentication } from '../hooks/useInheritedAuthentication';
|
||||
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Dropdown, type DropdownItem } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Input, type InputProps } from './core/Input';
|
||||
import { Link } from './core/Link';
|
||||
import { SegmentedControl } from './core/SegmentedControl';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { DynamicForm } from './DynamicForm';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
@@ -36,7 +31,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (authentication: Record<string, boolean>) => await patchModel(model, { authentication }),
|
||||
async (authentication: Record<string, unknown>) => await patchModel(model, { authentication }),
|
||||
[model],
|
||||
);
|
||||
|
||||
@@ -98,30 +93,65 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<HStack space={2} className="mb-2" alignItems="center">
|
||||
<Checkbox
|
||||
className="w-full"
|
||||
checked={!model.authentication.disabled}
|
||||
onChange={(disabled) => handleChange({ ...model.authentication, disabled: !disabled })}
|
||||
title="Enabled"
|
||||
/>
|
||||
{authConfig.data?.actions && authConfig.data.actions.length > 0 && (
|
||||
<Dropdown
|
||||
items={authConfig.data.actions.map(
|
||||
(a): DropdownItem => ({
|
||||
label: a.label,
|
||||
leftSlot: a.icon ? <Icon icon={a.icon} /> : null,
|
||||
onSelect: () => a.call(model),
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<IconButton title="Authentication Actions" icon="settings" size="xs" />
|
||||
</Dropdown>
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)] gap-y-3">
|
||||
<div>
|
||||
<HStack space={2} alignItems="start">
|
||||
<SegmentedControl
|
||||
label="Enabled"
|
||||
hideLabel
|
||||
name="enabled"
|
||||
value={
|
||||
model.authentication.disabled === false || model.authentication.disabled == null
|
||||
? '__TRUE__'
|
||||
: model.authentication.disabled === true
|
||||
? '__FALSE__'
|
||||
: '__DYNAMIC__'
|
||||
}
|
||||
options={[
|
||||
{ label: 'Enabled', value: '__TRUE__' },
|
||||
{ label: 'Disabled', value: '__FALSE__' },
|
||||
{ label: 'Enabled when...', value: '__DYNAMIC__' },
|
||||
]}
|
||||
onChange={async (enabled) => {
|
||||
let disabled: boolean | string;
|
||||
if (enabled === '__TRUE__') {
|
||||
disabled = false;
|
||||
} else if (enabled === '__FALSE__') {
|
||||
disabled = true;
|
||||
} else {
|
||||
disabled = '';
|
||||
}
|
||||
console.log('SETTING DISABLED', disabled);
|
||||
await handleChange({ ...model.authentication, disabled });
|
||||
}}
|
||||
/>
|
||||
{authConfig.data?.actions && authConfig.data.actions.length > 0 && (
|
||||
<Dropdown
|
||||
items={authConfig.data.actions.map(
|
||||
(a): DropdownItem => ({
|
||||
label: a.label,
|
||||
leftSlot: a.icon ? <Icon icon={a.icon} /> : null,
|
||||
onSelect: () => a.call(model),
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<IconButton title="Authentication Actions" icon="settings" size="xs" />
|
||||
</Dropdown>
|
||||
)}
|
||||
</HStack>
|
||||
{typeof model.authentication.disabled === 'string' && (
|
||||
<div className="mt-3">
|
||||
<AuthenticationDisabledInput
|
||||
className="w-full"
|
||||
stateKey={`auth.${model.id}.dynamic`}
|
||||
value={model.authentication.disabled}
|
||||
onChange={(v) => handleChange({ ...model.authentication, disabled: v })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</HStack>
|
||||
</div>
|
||||
<DynamicForm
|
||||
disabled={model.authentication.disabled}
|
||||
disabled={model.authentication.disabled === true}
|
||||
autocompleteVariables
|
||||
autocompleteFunctions
|
||||
stateKey={`auth.${model.id}.${model.authenticationType}`}
|
||||
@@ -132,3 +162,45 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthenticationDisabledInput({
|
||||
value,
|
||||
onChange,
|
||||
stateKey,
|
||||
className,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: InputProps['onChange'];
|
||||
stateKey: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const rendered = useRenderTemplate({
|
||||
template: value,
|
||||
enabled: true,
|
||||
purpose: 'preview',
|
||||
ignoreError: true,
|
||||
refreshKey: value,
|
||||
});
|
||||
|
||||
return (
|
||||
<Input
|
||||
size="sm"
|
||||
className={className}
|
||||
label="Dynamic Disabled"
|
||||
hideLabel
|
||||
defaultValue={value}
|
||||
placeholder="Enabled when this renders a non-empty value"
|
||||
rightSlot={
|
||||
<div className="px-1 flex items-center">
|
||||
<div className="rounded-full bg-surface-highlight text-xs px-1.5 py-0.5 text-text-subtle whitespace-nowrap">
|
||||
{rendered.isPending ? 'loading' : rendered.data ? 'enabled' : 'disabled'}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
autocompleteFunctions
|
||||
autocompleteVariables
|
||||
onChange={onChange}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,8 +67,10 @@ export function MarkdownEditor({
|
||||
<div className="absolute top-0 right-0 pt-1.5 pr-1.5">
|
||||
<SegmentedControl
|
||||
name={name}
|
||||
label="View mode"
|
||||
onChange={setViewMode}
|
||||
value={viewMode}
|
||||
className="opacity-0 group-focus-within/markdown:opacity-100 group-hover/markdown:opacity-100"
|
||||
options={[
|
||||
{ icon: 'eye', label: 'Preview mode', value: 'preview' },
|
||||
{ icon: 'pencil', label: 'Edit mode', value: 'edit' },
|
||||
|
||||
@@ -132,12 +132,13 @@ function InitializedTemplateFunctionDialog({
|
||||
|
||||
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 400);
|
||||
const [renderKey, setRenderKey] = useState<string | null>(null);
|
||||
const rendered = useRenderTemplate(
|
||||
debouncedTagText,
|
||||
previewType !== 'none',
|
||||
previewType === 'click' ? 'send' : 'preview',
|
||||
previewType === 'live' ? renderKey + debouncedTagText : renderKey,
|
||||
);
|
||||
const rendered = useRenderTemplate({
|
||||
template: debouncedTagText,
|
||||
enabled: previewType !== 'none',
|
||||
purpose: previewType === 'click' ? 'send' : 'preview',
|
||||
refreshKey: previewType === 'live' ? renderKey + debouncedTagText : renderKey,
|
||||
ignoreError: false,
|
||||
});
|
||||
|
||||
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Only update this on rendered data change to keep secrets hidden on input change
|
||||
|
||||
@@ -1,75 +1,122 @@
|
||||
import classNames from 'classnames';
|
||||
import { useRef } from 'react';
|
||||
import { type ReactNode, useRef } from 'react';
|
||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||
import { generateId } from '../../lib/generateId';
|
||||
import { Button } from './Button';
|
||||
import type { IconProps } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
import { IconButton, type IconButtonProps } from './IconButton';
|
||||
import { Label } from './Label';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
interface Props<T extends string> {
|
||||
options: { value: T; label: string; icon: IconProps['icon'] }[];
|
||||
options: { value: T; label: string; icon?: IconProps['icon'] }[];
|
||||
onChange: (value: T) => void;
|
||||
value: T;
|
||||
name: string;
|
||||
size?: IconButtonProps['size'];
|
||||
label: string;
|
||||
className?: string;
|
||||
hideLabel?: boolean;
|
||||
labelClassName?: string;
|
||||
help?: ReactNode;
|
||||
}
|
||||
|
||||
export function SegmentedControl<T extends string>({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
size = 'xs',
|
||||
label,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
help,
|
||||
className,
|
||||
}: Props<T>) {
|
||||
const [selectedValue, setSelectedValue] = useStateWithDeps<T>(value, [value]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const id = useRef(`input-${generateId()}`);
|
||||
|
||||
return (
|
||||
<HStack
|
||||
ref={containerRef}
|
||||
role="group"
|
||||
dir="ltr"
|
||||
space={0.5}
|
||||
className={classNames(
|
||||
className,
|
||||
'bg-surface-highlight rounded-md mb-auto opacity-0',
|
||||
'transition-opacity transform-gpu',
|
||||
'group-focus-within/markdown:opacity-100 group-hover/markdown:opacity-100',
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
const selectedIndex = options.findIndex((o) => o.value === selectedValue);
|
||||
if (e.key === 'ArrowRight') {
|
||||
const newIndex = Math.abs((selectedIndex + 1) % options.length);
|
||||
options[newIndex] && setSelectedValue(options[newIndex].value);
|
||||
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
|
||||
child.focus();
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
const newIndex = Math.abs((selectedIndex - 1) % options.length);
|
||||
options[newIndex] && setSelectedValue(options[newIndex].value);
|
||||
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
|
||||
child.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{options.map((o) => {
|
||||
const isSelected = selectedValue === o.value;
|
||||
const isActive = value === o.value;
|
||||
return (
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="solid"
|
||||
color={isActive ? 'secondary' : undefined}
|
||||
role="radio"
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={classNames(
|
||||
isActive && '!text-text',
|
||||
'!px-1.5 !w-auto',
|
||||
'focus:ring-border-focus',
|
||||
)}
|
||||
key={o.label}
|
||||
title={o.label}
|
||||
icon={o.icon}
|
||||
onClick={() => onChange(o.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
<div className="w-full grid">
|
||||
<Label
|
||||
htmlFor={id.current}
|
||||
help={help}
|
||||
visuallyHidden={hideLabel}
|
||||
className={classNames(labelClassName)}
|
||||
>
|
||||
{label}
|
||||
</Label>
|
||||
<HStack
|
||||
id={id.current}
|
||||
ref={containerRef}
|
||||
role="group"
|
||||
dir="ltr"
|
||||
space={1}
|
||||
className={classNames(
|
||||
className,
|
||||
'bg-surface-highlight rounded-lg mb-auto mr-auto',
|
||||
'transition-opacity transform-gpu p-1',
|
||||
)}
|
||||
onKeyDown={(e) => {
|
||||
const selectedIndex = options.findIndex((o) => o.value === selectedValue);
|
||||
if (e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
const newIndex = Math.abs((selectedIndex + 1) % options.length);
|
||||
options[newIndex] && setSelectedValue(options[newIndex].value);
|
||||
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
|
||||
child.focus();
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
const newIndex = Math.abs((selectedIndex - 1) % options.length);
|
||||
options[newIndex] && setSelectedValue(options[newIndex].value);
|
||||
const child = containerRef.current?.children[newIndex] as HTMLButtonElement;
|
||||
child.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{options.map((o) => {
|
||||
const isSelected = selectedValue === o.value;
|
||||
const isActive = value === o.value;
|
||||
if (o.icon == null) {
|
||||
return (
|
||||
<Button
|
||||
key={o.label}
|
||||
size={size}
|
||||
variant="solid"
|
||||
color={isActive ? 'secondary' : undefined}
|
||||
role="radio"
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={classNames(
|
||||
isActive && '!text-text',
|
||||
'focus:ring-1 focus:ring-border-focus',
|
||||
)}
|
||||
onClick={() => onChange(o.value)}
|
||||
>
|
||||
{o.label}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton
|
||||
key={o.label}
|
||||
size={size}
|
||||
variant="solid"
|
||||
color={isActive ? 'secondary' : undefined}
|
||||
role="radio"
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={classNames(
|
||||
isActive && '!text-text',
|
||||
'!px-1.5 !w-auto',
|
||||
'focus:ring-border-focus',
|
||||
)}
|
||||
title={o.label}
|
||||
icon={o.icon}
|
||||
onClick={() => onChange(o.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</HStack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ export function Select<T extends string>({
|
||||
onBlur={() => setFocused(false)}
|
||||
className={classNames(
|
||||
'pr-7 w-full outline-none bg-transparent disabled:opacity-disabled',
|
||||
'leading-[1]', // Center the text better vertically
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
||||
@@ -6,20 +6,33 @@ import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
|
||||
export function useRenderTemplate(
|
||||
template: string,
|
||||
enabled: boolean,
|
||||
purpose: RenderPurpose,
|
||||
refreshKey: string | null,
|
||||
) {
|
||||
export function useRenderTemplate({
|
||||
template,
|
||||
enabled,
|
||||
purpose,
|
||||
refreshKey,
|
||||
ignoreError,
|
||||
preservePreviousValue,
|
||||
}: {
|
||||
template: string;
|
||||
enabled: boolean;
|
||||
purpose: RenderPurpose;
|
||||
refreshKey?: string | null;
|
||||
ignoreError?: boolean;
|
||||
preservePreviousValue?: boolean;
|
||||
}) {
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom) ?? 'n/a';
|
||||
const environmentId = useActiveEnvironment()?.id ?? null;
|
||||
return useQuery<string>({
|
||||
refetchOnWindowFocus: false,
|
||||
enabled,
|
||||
queryKey: ['render_template', workspaceId, environmentId, refreshKey, purpose],
|
||||
placeholderData: preservePreviousValue ? (prev) => prev : undefined,
|
||||
queryKey: ['render_template', workspaceId, environmentId, refreshKey, purpose, ignoreError],
|
||||
queryFn: () =>
|
||||
minPromiseMillis(renderTemplate({ template, workspaceId, environmentId, purpose }), 300),
|
||||
minPromiseMillis(
|
||||
renderTemplate({ template, workspaceId, environmentId, purpose, ignoreError }),
|
||||
300,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,13 +41,21 @@ export async function renderTemplate({
|
||||
workspaceId,
|
||||
environmentId,
|
||||
purpose,
|
||||
ignoreError,
|
||||
}: {
|
||||
template: string;
|
||||
workspaceId: string;
|
||||
environmentId: string | null;
|
||||
purpose: RenderPurpose;
|
||||
ignoreError?: boolean;
|
||||
}): Promise<string> {
|
||||
return invokeCmd('cmd_render_template', { template, workspaceId, environmentId, purpose });
|
||||
return invokeCmd('cmd_render_template', {
|
||||
template,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
purpose,
|
||||
ignoreError,
|
||||
});
|
||||
}
|
||||
|
||||
export async function decryptTemplate({
|
||||
|
||||
Reference in New Issue
Block a user