import { useId } from "react"; import { PlusIcon } from "lucide-react"; import type { ScopeId, SecretBackedValue } from "@executor-js/sdk"; import { Button } from "../components/button"; import { CardStack, CardStackContent, CardStackEntry } from "../components/card-stack"; import { Field, FieldGroup, FieldLabel } from "../components/field"; import { Input } from "../components/input"; import { HeadersList } from "./headers-list"; import { CreatableSecretPicker, headerValueToState, headersFromState, type HeaderState, } from "./secret-header-auth"; import type { SecretPickerSecret } from "./secret-picker"; export type { SecretBackedValue }; export type QueryParamState = { name: string; secretId: string | null; prefix?: string; literalValue?: string; }; export type HttpCredentialsState = { headers: HeaderState[]; queryParams: QueryParamState[]; }; export const emptyHttpCredentials = (): HttpCredentialsState => ({ headers: [], queryParams: [], }); export const httpCredentialsFromValues = (input: { readonly headers?: Record | null; readonly queryParams?: Record | null; }): HttpCredentialsState => ({ headers: Object.entries(input.headers ?? {}).map(([name, value]) => headerValueToState(name, value), ), queryParams: Object.entries(input.queryParams ?? {}).map(([name, value]) => { if (typeof value === "string") { return { name, secretId: null, literalValue: value }; } return { name, secretId: value.secretId, prefix: value.prefix }; }), }); export const serializeHeaderCredentials = ( headers: readonly HeaderState[], ): Record => headersFromState(headers); export const serializeQueryCredentials = ( queryParams: readonly QueryParamState[], ): Record => { const result: Record = {}; for (const param of queryParams) { const name = param.name.trim(); if (!name) continue; if (param.secretId) { result[name] = { secretId: param.secretId, ...(param.prefix ? { prefix: param.prefix } : {}), }; continue; } if (param.literalValue?.trim()) { result[name] = param.literalValue.trim(); } } return result; }; export const serializeHttpCredentials = ( credentials: HttpCredentialsState, ): { readonly headers: Record; readonly queryParams: Record; } => ({ headers: serializeHeaderCredentials(credentials.headers), queryParams: serializeQueryCredentials(credentials.queryParams), }); export const httpCredentialsValid = (credentials: HttpCredentialsState): boolean => credentials.headers.every((header) => header.name.trim() && header.secretId) && credentials.queryParams.every((param) => { if (!param.name.trim()) return false; return Boolean(param.secretId || param.literalValue?.trim()); }); export function HttpCredentialsEditor(props: { readonly credentials: HttpCredentialsState; readonly onChange: (credentials: HttpCredentialsState) => void; readonly existingSecrets: readonly SecretPickerSecret[]; readonly sourceName?: string; readonly targetScope?: ScopeId; readonly writeScope?: ScopeId; readonly sections?: { readonly headers?: boolean; readonly queryParams?: boolean; }; readonly labels?: { readonly headers?: string; readonly queryParams?: string; }; }) { const showHeaders = props.sections?.headers ?? true; const showQueryParams = props.sections?.queryParams ?? true; return (
{showHeaders && (
{props.labels?.headers ?? "Headers"} props.onChange({ ...props.credentials, headers })} existingSecrets={props.existingSecrets} sourceName={props.sourceName} targetScope={props.targetScope ?? props.writeScope} />
)} {showQueryParams && (
{props.labels?.queryParams ?? "Query parameters"} props.onChange({ ...props.credentials, queryParams }) } existingSecrets={props.existingSecrets} sourceName={props.sourceName} targetScope={props.targetScope ?? props.writeScope} />
)}
); } function QueryParamsList(props: { readonly queryParams: readonly QueryParamState[]; readonly onQueryParamsChange: (queryParams: QueryParamState[]) => void; readonly existingSecrets: readonly SecretPickerSecret[]; readonly sourceName?: string; readonly targetScope?: ScopeId; }) { const addParam = () => { props.onQueryParamsChange([...props.queryParams, { name: "", secretId: null }]); }; const updateParam = (index: number, update: Partial) => { props.onQueryParamsChange( props.queryParams.map((param, i) => (i === index ? { ...param, ...update } : param)), ); }; const removeParam = (index: number) => { props.onQueryParamsChange(props.queryParams.filter((_, i) => i !== index)); }; return ( {props.queryParams.length === 0 ? ( No query parameters} onClick={addParam} /> ) : ( <> {props.queryParams.map((param, index) => ( updateParam(index, update)} onRemove={() => removeParam(index)} /> ))} )} ); } function QueryParamRow(props: { readonly param: QueryParamState; readonly existingSecrets: readonly SecretPickerSecret[]; readonly sourceName?: string; readonly targetScope?: ScopeId; readonly onChange: (update: Partial) => void; readonly onRemove: () => void; }) { const nameInputId = useId(); const prefixInputId = useId(); const literalInputId = useId(); const name = props.param.name.trim(); const secretLabel = name ? `${name} query parameter` : "Query parameter"; return (
Query parameter
Name props.onChange({ name: event.currentTarget.value })} placeholder="token" className="font-mono" /> Prefix (optional) props.onChange({ prefix: event.currentTarget.value || undefined })} placeholder="Bearer " className="font-mono" /> props.onChange({ secretId, literalValue: undefined })} secrets={props.existingSecrets} placeholder="Select a secret" sourceName={props.sourceName} secretLabel={secretLabel} targetScope={props.targetScope} /> {!props.param.secretId && props.param.literalValue !== undefined && ( Literal value props.onChange({ literalValue: event.currentTarget.value })} placeholder="value" className="font-mono" /> )}
); } function AddQueryParamRow(props: { readonly onClick: () => void; readonly leading?: React.ReactNode; }) { return ( ); }