import type { Environment } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models'; import type { GenericCompletionOption } from '@yaakapp-internal/plugins'; import classNames from 'classnames'; import { useCallback, useMemo } from 'react'; import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown'; import { useIsEncryptionEnabled } from '../hooks/useIsEncryptionEnabled'; import { useKeyValue } from '../hooks/useKeyValue'; import { useRandomKey } from '../hooks/useRandomKey'; import { analyzeTemplate, convertTemplateToSecure } from '../lib/encryption'; import { isBaseEnvironment } from '../lib/model_util'; import { setupOrConfigureEncryption, withEncryptionEnabled, } from '../lib/setupOrConfigureEncryption'; import { DismissibleBanner } from './core/DismissibleBanner'; import type { GenericCompletionConfig } from './core/Editor/genericCompletion'; import { Heading } from './core/Heading'; import type { PairEditorHandle, PairWithId } from './core/PairEditor'; import { ensurePairId } from './core/PairEditor.util'; import { PairOrBulkEditor } from './core/PairOrBulkEditor'; import { PillButton } from './core/PillButton'; import { EnvironmentColorIndicator } from './EnvironmentColorIndicator'; import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip'; interface Props { environment: Environment; hideName?: boolean; className?: string; setRef?: (n: PairEditorHandle | null) => void; } export function EnvironmentEditor({ environment, hideName, className, setRef }: Props) { const workspaceId = environment.workspaceId; const isEncryptionEnabled = useIsEncryptionEnabled(); const valueVisibility = useKeyValue({ namespace: 'global', key: ['environmentValueVisibility', workspaceId], fallback: false, }); const { allEnvironments } = useEnvironmentsBreakdown(); const handleChange = useCallback( (variables: PairWithId[]) => patchModel(environment, { variables }), [environment], ); const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey(); // Gather a list of env names from other environments to help the user get them aligned const nameAutocomplete = useMemo(() => { const options: GenericCompletionOption[] = []; if (isBaseEnvironment(environment)) { return { options }; } const allVariables = allEnvironments.flatMap((e) => e?.variables); const allVariableNames = new Set(allVariables.map((v) => v?.name)); for (const name of allVariableNames) { const containingEnvs = allEnvironments.filter((e) => e.variables.some((v) => v.name === name), ); const isAlreadyInActive = containingEnvs.find((e) => e.id === environment.id); if (isAlreadyInActive) { continue; } options.push({ label: name, type: 'constant', detail: containingEnvs.map((e) => e.name).join(', '), }); } return { options }; }, [environment, allEnvironments]); const validateName = useCallback((name: string) => { // Empty just means the variable doesn't have a name yet and is unusable if (name === '') return true; return name.match(/^[a-z_][a-z0-9_.-]*$/i) != null; }, []); const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password'; const allVariableAreEncrypted = useMemo( () => environment.variables.every((v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure'), [environment.variables], ); const encryptEnvironment = (environment: Environment) => { withEncryptionEnabled(async () => { const encryptedVariables: PairWithId[] = []; for (const variable of environment.variables) { const value = variable.value ? await convertTemplateToSecure(variable.value) : ''; encryptedVariables.push(ensurePairId({ ...variable, value })); } await handleChange(encryptedVariables); regenerateForceUpdateKey(); }); }; return (
{!hideName &&
{environment?.name}
} {isEncryptionEnabled ? ( !allVariableAreEncrypted ? ( encryptEnvironment(environment)}> Encrypt All Variables ) : ( Encryption Settings ) ) : ( valueVisibility.set((v) => !v)}> {valueVisibility.value ? 'Hide Values' : 'Show Values'} )} } onClick={async () => { await patchModel(environment, { public: !environment.public }); }} > {environment.public ? 'Sharable' : 'Private'}
{environment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && ( encryptEnvironment(environment), color: 'success', }, ]} > This sharable environment contains plain-text secrets )}
); }