mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 17:28:29 +02:00
Obfuscate environment variables
This commit is contained in:
@@ -5,6 +5,7 @@ import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
|||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
import { useEnvironments } from '../hooks/useEnvironments';
|
||||||
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||||
@@ -59,14 +60,16 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
<SidebarButton
|
<SidebarButton
|
||||||
active={selectedEnvironment?.id == null}
|
active={selectedEnvironment?.id == null}
|
||||||
onClick={() => setSelectedEnvironmentId(null)}
|
onClick={() => setSelectedEnvironmentId(null)}
|
||||||
className="group"
|
|
||||||
environment={null}
|
environment={null}
|
||||||
rightSlot={
|
rightSlot={
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
|
iconSize="md"
|
||||||
|
color="custom"
|
||||||
title="Add sub environment"
|
title="Add sub environment"
|
||||||
icon="plusCircle"
|
icon="plusCircle"
|
||||||
iconClassName="text-gray-500 group-hover:text-gray-700"
|
iconClassName="text-gray-500 group-hover:text-gray-700"
|
||||||
|
className="group"
|
||||||
onClick={handleCreateEnvironment}
|
onClick={handleCreateEnvironment}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -113,6 +116,11 @@ const EnvironmentEditor = function ({
|
|||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const valueVisibility = useKeyValue<boolean>({
|
||||||
|
namespace: 'global',
|
||||||
|
key: 'environmentValueVisibility',
|
||||||
|
fallback: true,
|
||||||
|
});
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
||||||
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
||||||
@@ -164,8 +172,17 @@ const EnvironmentEditor = function ({
|
|||||||
return (
|
return (
|
||||||
<VStack space={4} className={classNames(className, 'pl-4')}>
|
<VStack space={4} className={classNames(className, 'pl-4')}>
|
||||||
<HStack space={2} className="justify-between">
|
<HStack space={2} className="justify-between">
|
||||||
<Heading className="w-full flex items-center">
|
<Heading className="w-full flex items-center gap-1">
|
||||||
<div>{environment?.name ?? 'Global Variables'}</div>
|
<div>{environment?.name ?? 'Global Variables'}</div>
|
||||||
|
<IconButton
|
||||||
|
iconClassName="text-gray-600"
|
||||||
|
size="sm"
|
||||||
|
icon={valueVisibility.value ? 'eye' : 'eyeClosed'}
|
||||||
|
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
|
||||||
|
onClick={() => {
|
||||||
|
return valueVisibility.set((v) => !v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Heading>
|
</Heading>
|
||||||
</HStack>
|
</HStack>
|
||||||
<PairEditor
|
<PairEditor
|
||||||
@@ -173,6 +190,7 @@ const EnvironmentEditor = function ({
|
|||||||
nameAutocompleteVariables={false}
|
nameAutocompleteVariables={false}
|
||||||
namePlaceholder="VAR_NAME"
|
namePlaceholder="VAR_NAME"
|
||||||
nameValidate={validateName}
|
nameValidate={validateName}
|
||||||
|
valueType={valueVisibility.value ? 'text' : 'password'}
|
||||||
valueAutocompleteVariables={false}
|
valueAutocompleteVariables={false}
|
||||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||||
pairs={variables}
|
pairs={variables}
|
||||||
@@ -216,7 +234,7 @@ function SidebarButton({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'w-full grid grid-cols-[minmax(0,1fr)_auto] items-center',
|
'w-full grid grid-cols-[minmax(0,1fr)_auto] items-center gap-0.5',
|
||||||
'px-1', // Padding to show focus border
|
'px-1', // Padding to show focus border
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -225,7 +243,7 @@ function SidebarButton({
|
|||||||
size="xs"
|
size="xs"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'w-full',
|
'w-full',
|
||||||
active ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
|
active ? 'text-gray-800 bg-highlightSecondary' : 'text-gray-600 hover:text-gray-700',
|
||||||
)}
|
)}
|
||||||
justify="start"
|
justify="start"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
cloneElement,
|
cloneElement,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
memo,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
@@ -57,7 +56,7 @@ export interface EditorProps {
|
|||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
||||||
{
|
{
|
||||||
readOnly,
|
readOnly,
|
||||||
type = 'text',
|
type = 'text',
|
||||||
@@ -295,8 +294,6 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Editor = memo(_Editor);
|
|
||||||
|
|
||||||
function getExtensions({
|
function getExtensions({
|
||||||
container,
|
container,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useStateSyncDefault } from '../../hooks/useStateSyncDefault';
|
||||||
import type { EditorProps } from './Editor';
|
import type { EditorProps } from './Editor';
|
||||||
import { Editor } from './Editor';
|
import { Editor } from './Editor';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
@@ -69,7 +70,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
}: InputProps,
|
}: InputProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const [obscured, setObscured] = useState(type === 'password');
|
const [obscured, setObscured] = useStateSyncDefault(type === 'password');
|
||||||
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
|
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
|
|
||||||
@@ -181,9 +182,10 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
<IconButton
|
<IconButton
|
||||||
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
className="mr-0.5"
|
className="mr-0.5 group/obscure !h-auto my-0.5"
|
||||||
|
iconClassName="text-gray-500 group-hover/obscure:text-gray-800"
|
||||||
iconSize="sm"
|
iconSize="sm"
|
||||||
icon={obscured ? 'eyeClosed' : 'eye'}
|
icon={obscured ? 'eye' : 'eyeClosed'}
|
||||||
onClick={() => setObscured((o) => !o)}
|
onClick={() => setObscured((o) => !o)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type PairEditorProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
namePlaceholder?: string;
|
namePlaceholder?: string;
|
||||||
valuePlaceholder?: string;
|
valuePlaceholder?: string;
|
||||||
|
valueType?: 'text' | 'password';
|
||||||
nameAutocomplete?: GenericCompletionConfig;
|
nameAutocomplete?: GenericCompletionConfig;
|
||||||
valueAutocomplete?: (name: string) => GenericCompletionConfig | undefined;
|
valueAutocomplete?: (name: string) => GenericCompletionConfig | undefined;
|
||||||
nameAutocompleteVariables?: boolean;
|
nameAutocompleteVariables?: boolean;
|
||||||
@@ -51,6 +52,7 @@ export const PairEditor = memo(function PairEditor({
|
|||||||
nameAutocompleteVariables,
|
nameAutocompleteVariables,
|
||||||
namePlaceholder,
|
namePlaceholder,
|
||||||
nameValidate,
|
nameValidate,
|
||||||
|
valueType,
|
||||||
onChange,
|
onChange,
|
||||||
pairs: originalPairs,
|
pairs: originalPairs,
|
||||||
valueAutocomplete,
|
valueAutocomplete,
|
||||||
@@ -176,6 +178,7 @@ export const PairEditor = memo(function PairEditor({
|
|||||||
allowFileValues={allowFileValues}
|
allowFileValues={allowFileValues}
|
||||||
nameAutocompleteVariables={nameAutocompleteVariables}
|
nameAutocompleteVariables={nameAutocompleteVariables}
|
||||||
valueAutocompleteVariables={valueAutocompleteVariables}
|
valueAutocompleteVariables={valueAutocompleteVariables}
|
||||||
|
valueType={valueType}
|
||||||
forceFocusPairId={forceFocusPairId}
|
forceFocusPairId={forceFocusPairId}
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
nameAutocomplete={nameAutocomplete}
|
nameAutocomplete={nameAutocomplete}
|
||||||
@@ -218,6 +221,7 @@ type FormRowProps = {
|
|||||||
| 'valueAutocomplete'
|
| 'valueAutocomplete'
|
||||||
| 'nameAutocompleteVariables'
|
| 'nameAutocompleteVariables'
|
||||||
| 'valueAutocompleteVariables'
|
| 'valueAutocompleteVariables'
|
||||||
|
| 'valueType'
|
||||||
| 'namePlaceholder'
|
| 'namePlaceholder'
|
||||||
| 'valuePlaceholder'
|
| 'valuePlaceholder'
|
||||||
| 'nameValidate'
|
| 'nameValidate'
|
||||||
@@ -246,6 +250,7 @@ const FormRow = memo(function FormRow({
|
|||||||
valueAutocompleteVariables,
|
valueAutocompleteVariables,
|
||||||
valuePlaceholder,
|
valuePlaceholder,
|
||||||
valueValidate,
|
valueValidate,
|
||||||
|
valueType,
|
||||||
}: FormRowProps) {
|
}: FormRowProps) {
|
||||||
const { id } = pairContainer;
|
const { id } = pairContainer;
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@@ -397,6 +402,7 @@ const FormRow = memo(function FormRow({
|
|||||||
name="value"
|
name="value"
|
||||||
onChange={handleChangeValueText}
|
onChange={handleChangeValueText}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
|
type={isLast ? 'text' : valueType}
|
||||||
placeholder={valuePlaceholder ?? 'value'}
|
placeholder={valuePlaceholder ?? 'value'}
|
||||||
autocomplete={valueAutocomplete?.(pairContainer.pair.name)}
|
autocomplete={valueAutocomplete?.(pairContainer.pair.name)}
|
||||||
autocompleteVariables={valueAutocompleteVariables}
|
autocompleteVariables={valueAutocompleteVariables}
|
||||||
|
|||||||
12
src-web/hooks/useStateSyncDefault.ts
Normal file
12
src-web/hooks/useStateSyncDefault.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like useState, except it will update the value when the default value changes
|
||||||
|
*/
|
||||||
|
export function useStateSyncDefault<T>(defaultValue: T) {
|
||||||
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(defaultValue);
|
||||||
|
}, [defaultValue]);
|
||||||
|
return [value, setValue] as const;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user