import { useMemo, useState } from "react"; import { faArrowLeft, faChevronRight, faCodeCommit, faFolder, faMagnifyingGlass, faUndo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, ContentLoader, Input, Tag, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useGetSnapshotSecrets, usePerformSecretRollback } from "@app/hooks/api"; import { DecryptedSecret, TSecretFolder, UserWsKeyPair } from "@app/hooks/api/types"; import { renderIcon, SecretItem, TDiffModes, TDiffView } from "./SecretItem"; type Props = { snapshotId: string; environment: string; workspaceId: string; secretPath?: string; decryptFileKey: UserWsKeyPair; secrets?: DecryptedSecret[]; folders?: TSecretFolder[]; snapshotCount?: number; onGoBack: () => void; onClickListSnapshot: () => void; }; const LOADER_TEXT = ["Fetching your snapshot", "Creating the difference view"]; const deepCompareSecrets = (lhs: DecryptedSecret, rhs: DecryptedSecret) => lhs.key === rhs.key && lhs.value === rhs.value && lhs.comment === rhs.comment && lhs?.valueOverride === rhs?.valueOverride && JSON.stringify(lhs.tags) === JSON.stringify(rhs.tags); export const SnapshotView = ({ snapshotId, environment, workspaceId, secretPath, decryptFileKey, secrets = [], folders = [], onGoBack, snapshotCount, onClickListSnapshot }: Props) => { const { createNotification } = useNotificationContext(); const [search, setSearch] = useState(""); const { mutateAsync: performRollback, isLoading: isRollingBack } = usePerformSecretRollback(); const { data: snapshotData, isLoading: isSnapshotLoading } = useGetSnapshotSecrets({ snapshotId, env: environment, decryptFileKey }); const rollingFolder = snapshotData?.folders || []; const rollingSecrets = snapshotData?.secrets || []; const folderDiffView = useMemo(() => { const folderGroupById = folders.reduce>( (prev, curr) => ({ ...prev, [curr.id]: curr }), {} ); const diffView: Array> = []; rollingFolder.forEach((rollFolder) => { const { id, name } = rollFolder; const doesExist = Boolean(folderGroupById?.[id]); if (doesExist) { diffView.push({ mode: name === folderGroupById[id].name ? TDiffModes.NoChange : TDiffModes.Modified, pre: folderGroupById[id], post: rollFolder }); delete folderGroupById[id]; } else { diffView.push({ mode: TDiffModes.Created, post: rollFolder }); } }); Object.values(folderGroupById).forEach((folder) => { diffView.push({ mode: TDiffModes.Deleted, post: folder }); }); return diffView; }, [folders, rollingFolder]); const secretDiffView = useMemo(() => { const secretGroupById = secrets.reduce>( (prev, curr) => ({ ...prev, [curr.id]: curr }), {} ); const diffView: Array> = []; rollingSecrets.forEach((rollSecret) => { const { id: id } = rollSecret; const doesExist = Boolean(secretGroupById?.[id]); if (doesExist) { diffView.push({ mode: deepCompareSecrets(rollSecret, secretGroupById[id]) ? TDiffModes.NoChange : TDiffModes.Modified, pre: secretGroupById[id], post: rollSecret }); delete secretGroupById[id]; } else { diffView.push({ mode: TDiffModes.Created, post: rollSecret }); } }); Object.values(secretGroupById).forEach((folder) => { diffView.push({ mode: TDiffModes.Deleted, post: folder }); }); return diffView; }, [secrets, rollingSecrets]); const handleClickRollback = async () => { if (!snapshotData?.version) { createNotification({ text: "Failed to find secret version", type: "success" }); return; } try { await performRollback({ workspaceId, version: snapshotData.version, environment, directory: secretPath }); createNotification({ text: "Successfully rollback secrets", type: "success" }); onGoBack(); } catch (error) { console.log(error); createNotification({ text: "Failed to rollback secrets", type: "error" }); } }; if (isSnapshotLoading) { return ; } return ( <>
Snapshot
{new Date(snapshotData?.createdAt || "").toLocaleString()}
} value={search} onChange={(evt) => setSearch(evt.target.value)} />
{(isAllowed) => ( )}
Changes
{folderDiffView .sort((a, b) => a.post.name.toLowerCase().localeCompare(b.post.name.toLowerCase())) .filter((a) => a.post.name.toLowerCase().includes(search.toLowerCase())) .map(({ mode, pre, post }, index) => (
{renderIcon(mode)}
{mode === "modified" ? ( <>
{pre?.name}
{post?.name}
) : ( post.name )}
))} {secretDiffView .sort((a, b) => a.post.key.toLowerCase().localeCompare(b.post.key.toLowerCase())) .filter((a) => a.post.key.toLowerCase().includes(search.toLowerCase())) .map(({ mode, pre, post }, index) => ( ))}
); };