import { Controller, useFieldArray, useForm } from "react-hook-form"; import { subject } from "@casl/ability"; import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; import { faCheckCircle, faCircle, faCircleDot, faPlus, faTag } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, Drawer, DrawerContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, FormControl, IconButton, Input, SecretInput, Switch, Tag, TextArea, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context"; import { useGetSecretVersion } from "@app/hooks/api"; import { DecryptedSecret, UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; type Props = { isOpen?: boolean; environment: string; secretPath: string; onToggle: (isOpen: boolean) => void; onClose: () => void; secret: DecryptedSecret; decryptFileKey: UserWsKeyPair; onDeleteSecret: () => void; onSaveSecret: ( orgSec: DecryptedSecret, modSec: Omit & { tags: { _id: string }[] } ) => Promise; tags: WsTag[]; onCreateTag: () => void; }; export const SecretDetailSidebar = ({ isOpen, onToggle, decryptFileKey, secret, onDeleteSecret, onSaveSecret, tags, onCreateTag, environment, secretPath }: Props) => { const { createNotification } = useNotificationContext(); const { register, control, watch, handleSubmit, setValue, reset, formState: { isDirty, isSubmitting } } = useForm({ resolver: zodResolver(formSchema), values: secret }); const permission = useProjectPermission(); const cannotEditSecret = permission.cannot( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); const isReadOnly = permission.can( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ) && cannotEditSecret; const { fields, append, remove } = useFieldArray({ control, name: "tags" }); const selectedTags = watch("tags", []); const selectedTagsGroupById = selectedTags.reduce>( (prev, curr) => ({ ...prev, [curr._id]: true }), {} ); const overrideAction = watch("overrideAction"); const isOverridden = overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified; const { data: secretVersion } = useGetSecretVersion({ limit: 10, offset: 0, secretId: secret?._id, decryptFileKey }); const handleOverrideClick = () => { if (isOverridden) { // override need not be flagged delete if it was never saved in server // meaning a new unsaved personal secret but user toggled back later const isUnsavedOverride = !secret.idOverride; setValue( "overrideAction", isUnsavedOverride ? secret?.overrideAction : SecretActionType.Deleted, { shouldDirty: !isUnsavedOverride } ); setValue("valueOverride", secret?.valueOverride, { shouldDirty: !isUnsavedOverride }); } else { setValue("overrideAction", SecretActionType.Modified, { shouldDirty: true }); setValue("valueOverride", "", { shouldDirty: true }); } }; const handleTagSelect = (tag: WsTag) => { if (selectedTagsGroupById?.[tag._id]) { const tagPos = selectedTags.findIndex(({ _id }) => _id === tag._id); if (tagPos !== -1) { remove(tagPos); } } else { append(tag); } }; const handleFormSubmit = async (data: TFormSchema) => { await onSaveSecret(secret, { ...secret, ...data }); }; return ( { if (isOpen && isDirty) { if ( // eslint-disable-next-line no-alert window.confirm("You have edited the secret. Are you sure you want to reset the change?") ) { onToggle(false); reset(); } else return; } onToggle(state); }} isOpen={isOpen} >
{(isAllowed) => ( ( )} /> )}
{(isAllowed) => ( Override with a personal value )}
{isOverridden && ( ( )} /> )}
{fields.map(({ tagColor, id: formId, name, _id }) => ( { if (cannotEditSecret) { createNotification({ type: "error", text: "Access denied" }); return; } const tag = tags?.find(({ _id: id }) => id === _id); if (tag) handleTagSelect(tag); }} >
{name}
))} {(isAllowed) => ( )} Apply tags to this secrets {tags.map((tag) => { const { _id: tagId, name, tagColor } = tag; const isSelected = selectedTagsGroupById?.[tagId]; return ( handleTagSelect(tag)} key={tagId} icon={isSelected && } iconPos="right" >
{name}
); })} {(isAllowed) => ( )}