import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { subject } from "@casl/ability"; import { faClone, faKey, faSearch, faSquareCheck, faSquareXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, Checkbox, EmptyState, FormControl, IconButton, Input, Modal, ModalContent, ModalTrigger, Select, SelectItem, Skeleton, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useDebounce } from "@app/hooks"; import { useGetProjectSecrets } from "@app/hooks/api"; import { UserWsKeyPair } from "@app/hooks/api/types"; const formSchema = z.object({ environment: z.string().trim(), secretPath: z .string() .trim() .transform((val) => typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val ), secrets: z.record(z.string().optional().nullable()) }); type TFormSchema = z.infer; type Props = { isOpen?: boolean; isSmaller?: boolean; onToggle: (isOpen: boolean) => void; onParsedEnv: (env: Record) => void; environments?: { name: string; slug: string }[]; workspaceId: string; decryptFileKey: UserWsKeyPair; environment: string; secretPath: string; }; export const CopySecretsFromBoard = ({ environments = [], workspaceId, decryptFileKey, environment, secretPath, isOpen, isSmaller, onToggle, onParsedEnv }: Props) => { const [searchFilter, setSearchFilter] = useState(""); const [shouldIncludeValues, setShouldIncludeValues] = useState(true); const { handleSubmit, control, watch, register, reset, setValue, formState: { isDirty } } = useForm({ resolver: zodResolver(formSchema), defaultValues: { secretPath: "/", environment: environments?.[0]?.slug } }); const envCopySecPath = watch("secretPath"); const selectedEnvSlug = watch("environment"); const debouncedEnvCopySecretPath = useDebounce(envCopySecPath); const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({ workspaceId, environment: selectedEnvSlug, secretPath: debouncedEnvCopySecretPath, decryptFileKey, options: { enabled: Boolean(workspaceId) && Boolean(selectedEnvSlug) && Boolean(debouncedEnvCopySecretPath) && isOpen } }); useEffect(() => { setValue("secrets", {}); setSearchFilter(""); }, [debouncedEnvCopySecretPath]); const handleSecSelectAll = () => { if (secrets) { setValue( "secrets", secrets?.reduce((prev, curr) => ({ ...prev, [curr.key]: curr.value }), {}), { shouldDirty: true } ); } }; const handleFormSubmit = async (data: TFormSchema) => { const secretsToBePulled: Record = {}; Object.keys(data.secrets || {}).forEach((key) => { if (data.secrets[key]) { secretsToBePulled[key] = { value: (shouldIncludeValues && data.secrets[key]) || "", comments: [""] }; } }); onParsedEnv(secretsToBePulled); onToggle(false); reset(); }; return ( { onToggle(state); reset(); setSearchFilter(""); }} >
{(isAllowed) => ( )}
( )} />
Secrets
} onChange={(evt) => setSearchFilter(evt.target.value)} /> reset()} >
{!isSecretsLoading && !secrets?.length && ( )}
{isSecretsLoading && Array.apply(0, Array(2)).map((_x, i) => ( ))} {secrets ?.filter(({ key }) => key.toLowerCase().includes(searchFilter.toLowerCase())) ?.map(({ id, key, value: secVal }) => ( ( onChange(isChecked ? secVal : "")} > {key} )} /> ))}
setShouldIncludeValues(isChecked as boolean)} > Include secret values
); };